perf(db): batch tag inserts with prepared statements in set-media-tags! (P2)
ober
7960831beaf8141a33c434045369ab9bd5106668
--- a/src/imagesite/db.ss +++ b/src/imagesite/db.ss @@ -3,7 +3,10 @@ sqlite-finalize sqlite-step sqlite-query sqlite-execute) (only (std db sqlite) sqlite-open sqlite-close sqlite-exec sqlite-eval sqlite-query - sqlite-last-insert-rowid) + sqlite-last-insert-rowid + sqlite-prepare sqlite-step sqlite-reset sqlite-finalize + sqlite-bind! sqlite-column-value + SQLITE_ROW SQLITE_DONE) (only (jsqlite value) sql-null sql-null?) (only (imagesite util) safe-relative-path? relative-dirname path-basename @@ -332,14 +335,49 @@ (def (set-media-tags! db media-id tags) (sqlite-eval db "DELETE FROM media_tags WHERE media_id = ?" media-id) - (for-each - (lambda (tag) - (when (string? tag) - (let ((tag-id (ensure-tag! db tag))) - (when tag-id - (sqlite-eval db "INSERT OR IGNORE INTO media_tags (media_id, tag_id) VALUES (?, ?)" - media-id tag-id))))) - tags)) + (let ((tag-insert (sqlite-prepare db "INSERT OR IGNORE INTO tags (name, slug) VALUES (?, ?)")) + (tag-select (sqlite-prepare db "SELECT id FROM tags WHERE name = ?")) + (tag-select-slug (sqlite-prepare db "SELECT id FROM tags WHERE slug = ?")) + (mt-insert (sqlite-prepare db "INSERT OR IGNORE INTO media_tags (media_id, tag_id) VALUES (?, ?)"))) + (unwind-protect + (for-each + (lambda (tag) + (when (string? tag) + (let ((clean (string-trim tag))) + (unless (string-blank? clean) + (let ((slug (slugify clean))) + (sqlite-bind! tag-select 1 clean) + (let ((tag-id (if (= (sqlite-step tag-select) SQLITE_ROW) + (sqlite-column-value tag-select 0) + #f))) + (sqlite-reset tag-select) + (unless tag-id + (sqlite-bind! tag-insert 1 clean) + (sqlite-bind! tag-insert 2 slug) + (sqlite-step tag-insert) + (sqlite-reset tag-insert) + (sqlite-bind! tag-select 1 clean) + (set! tag-id (if (= (sqlite-step tag-select) SQLITE_ROW) + (sqlite-column-value tag-select 0) + #f)) + (sqlite-reset tag-select) + (unless tag-id + (sqlite-bind! tag-select-slug 1 slug) + (set! tag-id (if (= (sqlite-step tag-select-slug) SQLITE_ROW) + (sqlite-column-value tag-select-slug 0) + #f)) + (sqlite-reset tag-select-slug))) + (when tag-id + (sqlite-bind! mt-insert 1 media-id) + (sqlite-bind! mt-insert 2 tag-id) + (sqlite-step mt-insert) + (sqlite-reset mt-insert)))))))) + tags) + (begin + (sqlite-finalize tag-insert) + (sqlite-finalize tag-select) + (sqlite-finalize tag-select-slug) + (sqlite-finalize mt-insert))))) (def (album-row->hash row) (object --- a/tests/imagesite/db-test.ss +++ b/tests/imagesite/db-test.ss @@ -68,6 +68,23 @@ (chk (length (media-by-kind db "video")) => 1) (mark-all-media-missing! db) (chk (hash-get (counts db) "media") => 0) + (let ((r3 (upsert-media! db "family/tagged.jpg" 300 126))) + (chk (car r3) => 'inserted) + (let ((mid (cdr r3))) + (set-media-tags! db mid '("alpha" "beta" "gamma" "delta" "epsilon")) + (chk (length (media-tags db mid)) => 5) + (chk (media-tags db mid) => '("alpha" "beta" "delta" "epsilon" "gamma")) + (set-media-tags! db mid '("alpha" "zeta")) + (chk (length (media-tags db mid)) => 2) + (chk (media-tags db mid) => '("alpha" "zeta")) + (set-media-tags! db mid '()) + (chk (length (media-tags db mid)) => 0) + (with-imagesite-transaction + db + (lambda () + (set-media-tags! db mid '("txn-a" "txn-b" "txn-c")))) + (chk (length (media-tags db mid)) => 3) + (chk (media-tags db mid) => '("txn-a" "txn-b" "txn-c")))) (imagesite-close db)) (delete-if-exists path) (delete-if-exists "var/test/db-test.db-wal")