Added support for importing/exporting/deleting system documents.
@@ -104,11 +104,11 @@ of opRun:
LS.serve runForever() of opImport: - LS.store.importDir(LS.directory) + LS.store.importDir(LS.directory, LS.manageSystemData) of opExport: - LS.store.exportDir(LS.directory) + LS.store.exportDir(LS.directory, LS.manageSystemData) of opDelete: - LS.store.deleteDir(LS.directory) + LS.store.deleteDir(LS.directory, LS.manageSystemData) of opOptimize: LS.store.optimize of opExecute:
@@ -17,6 +17,7 @@ operation = opRun
directory:string = "" readonly = false logLevel = "warn" + system = false mount = false auth = newJNull() exOperation:string = ""@@ -47,7 +48,7 @@ Options:
-a, --address Specify server address (default: 127.0.0.1). --auth Specify an authentication/authorization configuration file. -b, --body Specify a string containing input data for an operation to be executed. - -c, --custom Specify a path containing custom resource definitions. + -c, --custom Specify a path to a folder containing custom resource definitions. -d, --directory Specify a directory to serve, import, export, delete, or mount. -f, --file Specify a file containing input data for an operation to be executed. -h, --help Display this message.@@ -57,6 +58,7 @@ -o, --operation Specify an operation to execute via the execute command: get, put, delete, patch, post, head, options.
-p, --port Specify server port number (default: 9500). -r, --readonly Allow only data retrieval operations. -s, --store Specify a datastore file (default: data.db) + --system Set the system flag for import, export, and delete operations -t, --type Specify a content type for the body an operation to be executed via the execute command. -u, --uri Specify an uri to execute an operation through the execute command. -v, --version Display the program version.@@ -185,6 +187,7 @@ LS.readonly = readonly
LS.favicon = favicon LS.loglevel = loglevel LS.auth = auth +LS.manageSystemData = system LS.customResources = customResources LS.mount = mount LS.execution.file = exFile
@@ -323,6 +323,38 @@ store.rollback()
eWarn() raise +proc createSystemDocument*(store: Datastore, id = "", rawdata = "", + contenttype = "text/plain", binary = -1): string = + let singleOp = not LS_TRANSACTION + var id = id + var contenttype = contenttype.replace(peg"""\;(.+)$""", "") # Strip charset for now + var binary = checkIfBinary(binary, contenttype) + var data = rawdata + if contenttype == "application/json": + # Validate JSON data + try: + discard data.parseJson + except: + raise newException(JsonParsingError, "Invalid JSON content - " & + getCurrentExceptionMsg()) + if id == "": + id = $genOid() + elif id.isFolder: + id = id & $genOid() + # Store document + try: + LOG.debug("Creating system document '$1'" % id) + store.begin() + discard store.db.insertID(SQL_INSERT_SYSTEM_DOCUMENT, id, data, contenttype, + binary, currentTime()) + if singleOp: + store.commit() + return $store.retrieveRawDocument(id) + except: + store.rollback() + eWarn() + raise + proc updateDocument*(store: Datastore, id: string, rawdata: string, contenttype = "text/plain", binary = -1, searchable = 1): string = let singleOp = not LS_TRANSACTION@@ -424,7 +456,7 @@
proc countDocuments*(store: Datastore): int64 = return store.db.getRow(SQL_COUNT_DOCUMENTS)[0].parseInt -proc importFile*(store: Datastore, f: string, dir = "") = +proc importFile*(store: Datastore, f: string, dir = "", system = false) = if not f.fileExists: raise newException(EFileNotFound, "File '$1' not found." % f) let ext = f.splitFile.ext@@ -442,8 +474,11 @@ d_contents = d_contents.encode(d_contents.len*2) # Encode in Base64.
let singleOp = not LS_TRANSACTION store.begin() try: - discard store.createDocument(d_id, d_contents, d_ct, d_binary, d_searchable) - if dir != "": + if system: + discard store.createSystemDocument(d_id, d_contents, d_ct, d_binary) + else: + discard store.createDocument(d_id, d_contents, d_ct, d_binary, d_searchable) + if dir != "" and not system: store.db.exec(SQL_INSERT_TAG, "$dir:"&dir, d_id) except: store.rollback()@@ -476,7 +511,7 @@ eWarn()
quit(203) quit(0) -proc importDir*(store: Datastore, dir: string) = +proc importDir*(store: Datastore, dir: string, system = false) = var files = newSeq[string]() if not dir.dirExists: raise newException(EDirectoryNotFound, "Directory '$1' not found." % dir)@@ -498,7 +533,7 @@ LOG.debug("Dropping column indexes...")
store.db.dropIndexes() for f in files: try: - store.importFile(f, dir) + store.importFile(f, dir, system) cFiles.inc if (cFiles-1) mod batchSize == 0: cBatches.inc@@ -514,8 +549,12 @@ store.db.createIndexes()
store.commit() LOG.info("Imported $1/$2 files", cFiles, files.len) -proc exportDir*(store: Datastore, dir: string) = - let docs = store.db.getAllRows(SQL_SELECT_DOCUMENTS_BY_TAG, "$dir:"&dir) +proc exportDir*(store: Datastore, dir: string, system = false) = + var docs: seq[Row] + if system: + docs = store.db.getAllRows(SQL_SELECT_SYSTEM_DOCUMENTS) + else: + docs = store.db.getAllRows(SQL_SELECT_DOCUMENTS_BY_TAG, "$dir:"&dir) LOG.info("Exporting $1 files...", docs.len) for doc in docs: LOG.debug("Exporting: $1", doc[1])@@ -529,12 +568,15 @@ file.parentDir.createDir
file.writeFile(data) LOG.info("Done."); -proc deleteDir*(store: Datastore, dir: string) = - store.db.exec(SQL_DELETE_SEARCHDATA_BY_TAG, "$dir:"&dir) - store.db.exec(SQL_DELETE_DOCUMENTS_BY_TAG, "$dir:"&dir) - store.db.exec(SQL_DELETE_TAGS_BY_TAG, "$dir:"&dir) - let total = store.db.getRow(SQL_COUNT_DOCUMENTS)[0].parseInt - store.db.exec(SQL_SET_TOTAL_DOCS, total) +proc deleteDir*(store: Datastore, dir: string, system = false) = + if system: + store.db.exec(SQL_DELETE_SYSTEM_DOCUMENTS) + else: + store.db.exec(SQL_DELETE_SEARCHDATA_BY_TAG, "$dir:"&dir) + store.db.exec(SQL_DELETE_DOCUMENTS_BY_TAG, "$dir:"&dir) + store.db.exec(SQL_DELETE_TAGS_BY_TAG, "$dir:"&dir) + let total = store.db.getRow(SQL_COUNT_DOCUMENTS)[0].parseInt + store.db.exec(SQL_SET_TOTAL_DOCS, total) proc mountDir*(store: var Datastore, dir: string) = if not dir.dirExists:
@@ -105,6 +105,12 @@ (id, data, content_type, binary, searchable, created)
VALUES (?, ?, ?, ?, ?, ?) """ +const SQL_INSERT_SYSTEM_DOCUMENT* = sql""" +INSERT INTO system_documents +(id, data, content_type, binary, created) +VALUES (?, ?, ?, ?, ?) +""" + const SQL_UPDATE_DOCUMENT* = sql""" UPDATE documents SET data = ?,@@ -175,6 +181,10 @@ WHERE documents.id = tags.document_id AND
tag_id = ? """ +const SQL_SELECT_SYSTEM_DOCUMENTS* = sql""" +SELECT * FROM system_documents +""" + const SQL_SELECT_DOCUMENT_IDS_BY_TAG* = sql""" SELECT id FROM documents, tags WHERE documents.id = tags.document_id AND@@ -202,6 +212,10 @@ const SQL_DELETE_DOCUMENTS_BY_TAG* = sql"""
DELETE FROM documents WHERE documents.id IN (SELECT document_id FROM tags WHERE tag_id = ?) +""" + +const SQL_DELETE_SYSTEM_DOCUMENTS* = sql""" +DELETE FROM system_documents """ const SQL_DELETE_SEARCHDATA_BY_TAG* = sql"""
@@ -32,6 +32,7 @@ jsonFilter*: string
jsonSelect*: seq[tuple[path: string, alias: string]] select*: seq[string] single*:bool + system*:bool limit*: int offset*: int orderby*: string@@ -71,6 +72,7 @@ address*: string
port*: int operation*: Operation directory*: string + manageSystemData*: bool file*: string mount*: bool readonly*: bool
@@ -60,6 +60,9 @@ raise newException(EInvalidTag, "Invalid Tag '$1'" % tag)
result = result & "AND " & doc_id_col & " IN (" & select_tagged & tag & "') " proc prepareSelectDocumentsQuery*(options: var QueryOptions): string = + var documents_table = "documents" + if options.system: + documents_table = "system_documents" var tables = options.tables result = "SELECT " if options.jsonFilter.len > 0 or options.jsonSelect.len > 0:@@ -84,23 +87,23 @@ if options.limit > 0:
innerSelect = innerSelect & "LIMIT " & $options.limit if options.offset > 0: innerSelect = innerSelect & " OFFSET " & $options.offset - tables = options.tables & @["documents"] + tables = options.tables & @[documents_table] result = result & options.select.join(", ") result = result & " FROM " & tables.join(", ") & " JOIN (" & innerSelect & ") as ranktable USING(docid) JOIN searchdata USING(docid) " result = result & "WHERE 1=1 " else: tables = options.tables & @["searchdata"] if options.jsonFilter != "": - options.select[0] = "COUNT(documents.docid)" - tables = tables & @["documents"] + options.select[0] = "COUNT($1.docid)" % documents_table + tables = tables & @[documents_table] result = result & options.select.join(", ") result = result & " FROM "&tables.join(", ")&" " result = result & "WHERE 1=1 " if options.jsonFilter != "": - result = result & "AND documents.id = searchdata.id " + result = result & "AND $1.id = searchdata.id " % documents_table options.orderby = "" else: - tables = options.tables & @["documents"] + tables = options.tables & @[documents_table] result = result & options.select.join(", ") result = result & " FROM "&tables.join(", ")&" WHERE 1=1 " if options.single:@@ -108,7 +111,7 @@ result = result & "AND id = ?"
var doc_id_col: string if options.tags.len > 0 or options.folder.len > 0: if options.jsonFilter.len > 0 or (options.search.len > 0 and options.select[0] != "COUNT(docid)"): - doc_id_col = "documents.id" + doc_id_col = "$1.id" % documents_table else: doc_id_col = "id" if options.createdAfter != "":