9 @@BASEPATH = '/arc/quanlib' # TODO: FIXME: configure this in a sane way
10 @@UNCLASSIFIED_CSV = @@BASEPATH + '/unclassified.csv'
12 def self.unclassified_csv
19 #@dburl = 'dbi:Pg:quanlib:localhost'
28 # @conn = PGconn.connect('localhost', 5432, '', '', 'quanlib', 'quanlib', 'quanlib')
29 @conn = PG.connect('localhost', 5432, '', '', 'quanlib', 'quanlib', 'quanlib')
37 def construct_efs_path(efs_id)
38 id_str = sprintf('%010d', efs_id)
39 path = sprintf('%s/%s/%s/%s', id_str[0,2], id_str[2,2], id_str[4,2], id_str[6,2])
40 name = id_str + '.dat'
47 CREATE TABLE Authors (
48 id INTEGER PRIMARY KEY,
58 id INTEGER PRIMARY KEY,
59 author INTEGER REFERENCES Authors(id),
60 classification INTEGER REFERENCES Classifications(id),
64 series INTEGER REFERENCES Series(id),
70 create_classification =
72 CREATE TABLE Classifications (
73 id INTEGER PRIMARY KEY,
76 author_grouping VARCHAR(64),
77 author_sort VARCHAR(128),
78 title_grouping VARCHAR(256),
94 id VARCHAR(32) PRIMARY KEY,
99 # Associative entity, linking FAST and Classifications tables
100 # in a 0..n to 0..m relationship
101 create_fast_classifications =
103 CREATE TABLE FAST_Classifications (
104 fast VARCHAR(32) REFERENCES FAST(id),
105 classification INTEGER REFERENCES Classifications(id)
111 CREATE TABLE Series (
112 id INTEGER PRIMARY KEY,
115 grouping VARCHAR(64),
123 create_classification,
128 create_fast_classifications,
129 'CREATE SEQUENCE author_id;',
130 'CREATE SEQUENCE book_id;',
131 'CREATE SEQUENCE classification_id;',
132 'CREATE SEQUENCE efs_id;',
133 'CREATE SEQUENCE series_id;'
140 populate_fast_table()
141 populate_classifications_table()
142 populate_series_table()
148 'DROP TABLE FAST_Classifications;',
149 'DROP TABLE Authors;',
150 'DROP TABLE Classifications;',
153 'DROP TABLE Series;',
154 'DROP SEQUENCE author_id;',
155 'DROP SEQUENCE book_id;',
156 'DROP SEQUENCE classification_id;',
157 'DROP SEQUENCE efs_id;',
158 'DROP SEQUENCE series_id;'
164 rescue Exception => exc
165 puts 'WARNING: "' + stmt + '" failed: ' + exc.to_s
170 def find_author(author)
171 sqlSelect = "SELECT id FROM Authors WHERE grouping=$1 AND reading=$2 AND sort=$3;"
172 args = [author.grouping, author.reading_order, author.sort_order]
173 @conn.exec_params(sqlSelect, args) do |rs|
182 sql = "SELECT 1 FROM pg_tables WHERE tableowner='quanlib' AND tablename='books'"
184 @conn.exec(sql).each do |row|
194 #puts 'DEBUG: load_author(' + id + ')'
195 sqlSelect = "SELECT grouping, reading, sort FROM Authors WHERE id=$1"
197 @conn.exec_params(sqlSelect, args) do |rs|
199 raise "Expected 1 row for " + id + " but got " + rs.ntuples + ": " + sqlSelect
202 author = Author.new(row['grouping'], row['reading'], row['sort'])
203 #puts 'DEBUG: author: ' + author.inspect()
206 #puts 'DEBUG: NOT FOUND'
210 def store_author(author)
211 id = find_author(author)
213 id = next_id('author_id')
214 sqlInsert = "INSERT INTO Authors(id, grouping, reading, sort) VALUES ($1, $2, $3, $4);"
215 args = [id, author.grouping, author.reading_order, author.sort_order]
217 rs = @conn.exec_params(sqlInsert, args)
218 rescue Exception => e
219 puts sqlInsert + ": " + args.inspect()
230 #puts 'DEBUG: load_book(' + id + ')'
231 sql = "SELECT author, classification, cover, description, path, series, title, volume FROM Books WHERE id=$1;"
235 @conn.exec_params(sql, [id]) do |rs|
237 raise 'Expected one row in Books for id ' + id + ', but found ' + rs.length + '.'
242 book = Book.new(self)
243 book.author = load_author(row['author'])
244 book.classification_id = row['classification']
245 book.cover = load_cover(row['cover'])
246 book.description = row['description']
247 book.path = row['path']
248 book.series_id = row['series']
249 book.title = row['title']
250 book.volume = row['volume']
252 rescue Exception => e
258 #puts 'DEBUG: loaded book: ' + book.inspect()
263 sql = "INSERT INTO Books (id, author, classification, cover, description, path, series, title, volume) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);"
265 book_id = next_id('book_id')
267 author_id = store_author(book.author)
268 (efs_id, mime_type) = store_cover(book)
270 args = [book_id, author_id, book.classification_id, efs_id, book.description(), book.path(), book.series_id(), book.title(), book.volume()]
273 rs = @conn.exec_params(sql, args)
274 rescue Exception => e
275 puts sql + ": " + args.inspect()
285 def find_classification(author_grouping, title_grouping)
286 #puts 'find_classification("' + author_grouping.inspect + '", "' + title_grouping.inspect + '")...'
287 sql = "SELECT id FROM Classifications WHERE author_grouping = $1 AND title_grouping = $2;"
288 @conn.exec_params(sql, [author_grouping, title_grouping]) do |rs|
290 #puts ' --> ' + rs[0]['id'].inspect
298 def load_classification(id)
299 sql = "SELECT ddc, lcc, author_grouping, author_sort, title_grouping, title "
300 sql += " FROM Classifications WHERE id=$1"
301 @conn.exec_params(sql, [id]) do |rs|
306 author_grouping = row['author_grouping']
307 author = row['author_sort']
308 title_grouping = row['title_grouping']
311 result = Classification.new(ddc, lcc, author_grouping, author, title_grouping, title)
325 mime_type = 'application/octet-stream'
327 sql = "SELECT mimeType FROM Efs WHERE id=$1"
328 @conn.exec_params(sql, [id]) do |rs|
330 raise "Expected one row but got " + rs.ntuples + ": " + sql + ": " + id
332 mime_type = rs[0]['mimeType']
335 (efspath, efsname) = construct_efs_path(id)
337 fullpath = @@BASEPATH + '/efs/' + efspath + '/' + efsname
339 return Cover.new(nil, fullpath, mime_type)
341 #File.open(fullpath, 'rb') do |is|
342 # return Cover.new(is, fullpath, mime_type)
348 def store_cover(book)
356 @conn.exec("SELECT nextval('efs_id')") do |rs|
357 efs_id = rs[0]['nextval']
364 (efspath, efsname) = construct_efs_path(efs_id)
366 efspath = @@BASEPATH + '/efs/' + efspath
368 FileUtils.mkdir_p(efspath)
370 (filepath, mimetype) = cover.write_image(efspath, efsname)
372 sql = "INSERT INTO efs VALUES ($1, $2)"
374 rs = @conn.exec_params(sql, [efs_id, mimetype])
375 rescue Exception => e
376 puts sql + ": " + efs_id + ", " + mimetype
383 return efs_id, mimetype
386 def exec_id_query(sql, args)
388 @conn.exec_params(sql, args) do |rs|
396 def exec_update(sql, args)
398 rs = @conn.exec_params(sql, args)
399 rescue Exception => e
400 puts sql + ": " + args.inspect()
408 def next_id(seq_name)
410 @conn.exec("SELECT nextval('" + seq_name + "');") do |rs|
411 id = rs[0]['nextval']
416 def get_series(grouping, code)
421 sql = "SELECT id FROM Series WHERE grouping=$1 AND code=$2;"
422 args = [grouping, code]
423 @conn.exec_params(sql, args).each do |row|
427 # TODO: Create a new series object here?
428 puts 'WARNING: series("' + grouping + '", "' + code + '") not found.'
433 sql = "SELECT age,genre,grouping,code,descr FROM Series WHERE id=$1;"
435 @conn.exec_params(sql, args) do |rs|
438 series = Series.new(id)
439 series.age = row['age']
440 series.genre = row['genre']
441 series.grouping = row['grouping']
442 series.code = row['code']
443 series.descr = row['descr']
450 def populate_classifications_table
451 puts "Populating the Classifications table..."
453 CSV.foreach(@@BASEPATH + '/csv/class.csv') do |row|
455 # skip the header row
459 # First, add a row to the Classifications table
461 id = next_id('classification_id')
464 author_grouping = row[2]
466 title_grouping = row[4]
469 sqlInsert = "INSERT INTO Classifications (id, ddc, lcc, author_grouping, author_sort, title_grouping, title) VALUES ($1, $2, $3, $4, $5, $6, $7);"
470 args = [id, ddc, lcc, author_grouping, author_sort, title_grouping, title]
471 exec_update(sqlInsert, args)
473 # Second, link up with the appropriate FAST table entries
478 fast = input.split(';')
481 fast.each do |fast_id|
482 sqlInsert = "INSERT INTO FAST_Classifications (fast, classification) VALUES ($1, $2);"
484 exec_update(sqlInsert, args)
490 def populate_fast_table
491 puts "Populating the FAST table..."
493 CSV.foreach(@@BASEPATH + '/csv/fast.csv') do |row|
495 first = false # skip the header row
499 sqlInsert = "INSERT INTO FAST (id, descr) VALUES ($1, $2);"
500 exec_update(sqlInsert, [id, descr])
505 def populate_series_table
506 puts "Populating the Series table..."
507 CSV.foreach(@@BASEPATH + '/csv/series.csv') do |row|
508 id = next_id('series_id')
509 sqlInsert = "INSERT INTO Series (id, age, genre, grouping, code, descr) VALUES ($1, $2, $3, $4, $5, $6);"
511 exec_update(sqlInsert, args)
515 def query_books_by_author(pattern)
518 SELECT b.id FROM Authors a
519 INNER JOIN Books b ON b.author=a.id
520 LEFT OUTER JOIN Series s on s.id=b.series
521 WHERE upper(a.grouping) LIKE $1
522 ORDER BY a.grouping, b.series, b.volume, b.title
524 return exec_id_query(sql, [pattern])
527 def query_books_by_ddc
530 SELECT b.id FROM Classifications c
531 INNER JOIN Books b ON b.classification=c.id
534 return exec_id_query(sql, [])
537 def query_books_by_series_id(id)
540 SELECT b.id FROM Books b
542 ORDER BY b.volume,b.title
544 return exec_id_query(sql, [id])
547 def query_series_by_age(pattern)
553 ORDER BY s.grouping,s.descr
555 return exec_id_query(sql, [pattern])