all repos — litestore @ b55f93779979da1b976ad89ed0698953c3afbaf0

A minimalist nosql document store.

Implemented support for filter query option.
h3rald h3rald@h3rald.com
Sun, 04 Feb 2018 16:48:45 +0100
commit

b55f93779979da1b976ad89ed0698953c3afbaf0

parent

b9fc473b283782f48aa9301c264ce3491d9b16fa

3 files changed, 39 insertions(+), 9 deletions(-)

jump to
M lib/api_v3.nimlib/api_v3.nim

@@ -38,12 +38,13 @@ table["gt"] = ">"

table["gte"] = ">=" table["lt"] = "<" table["lte"] = "<=" + table["contains"] = "contains" return table[op] -proc filterClauses*(str: string): string = +proc filterClauses*(str: string, options: var QueryOptions): string = let tokens = """ - operator <- 'not eq' / 'eq' / 'gt' / 'gte' / 'lt' / 'lte' + operator <- 'not eq' / 'eq' / 'gte' / 'gt' / 'lte' / 'lt' / 'contains' value <- string / number / 'null' / 'true' / 'false' string <- '"' ('\"' . / [^"])* '"' number <- '-'? '0' / [1-9] [0-9]* ('.' [0-9]+)? (( 'e' / 'E' ) ( '+' / '-' )? [0-9]+)?

@@ -78,12 +79,28 @@ if not andClause.isNil:

var clauses = newSeq[string](3) discard andClause.strip.match(clause, clauses) clauses[1] = sqlOp(clauses[1]) + if clauses[2] == "true": + clauses[2] = "1" + elif clauses[2] == "false": + clauses[2] = "0" parsedAndClauses.add clauses if parsedAndClauses.len > 0: parsedClauses.add parsedAndClauses if parsedClauses.len == 0: return "" - return parsedClauses.mapIt(it.mapIt("json_extract(documents.data, '$1') $2 $3" % it).join(" AND ")).join(" OR ") + var currentArr = 0 + var tables = newSeq[string]() + let resOrClauses = parsedClauses.map do (it: seq[seq[string]]) -> string: + let resAndClauses = it.map do (x: seq[string]) -> string: + if x[1] == "contains": + currentArr = currentArr + 1 + tables.add "json_each(documents.data, '$1') AS arr$2" % [x[0], $currentArr] + return "arr$1.value == $2" % [$currentArr, x[2]] + else: + return "json_extract(documents.data, '$1') $2 $3" % x + return resAndClauses.join(" AND ") + options.tables = options.tables & tables + return resOrClauses.join(" OR ") proc parseQueryOption*(fragment: string, options: var QueryOptions) = var pair = fragment.split('=')

@@ -95,7 +112,9 @@ except:

raise newException(EInvalidRequest, "Unable to decode query string fragment '$1'" % fragment) case pair[0]: of "filter": - options.jsonFilter = filterClauses(pair[1]) + options.jsonFilter = filterClauses(pair[1], options) + if options.jsonFilter == "": + raise newException(EInvalidRequest, "Invalid filter clause: $1" % pair[1].replace("\"", "\\\"")) of "search": options.search = pair[1] of "tags":

@@ -451,7 +470,7 @@ return LS.getDocument(id, options)

else: return LS.getRawDocuments(options) except: - return resError(Http500, "Internal Server Error - $1" % getCurrentExceptionMsg()) + return resError(Http400, "Bad Request - $1" % getCurrentExceptionMsg()) of "info": if id != "": return resError(Http404, "Info '$1' not found." % id)
M lib/types.nimlib/types.nim

@@ -21,6 +21,7 @@ db*: DbConn

path*: string mount*: string QueryOptions* = object + tables*: seq[string] jsonFilter*: string select*: seq[string] single*:bool

@@ -99,4 +100,4 @@ "Server": LS.appname & "/" & LS.appversion

} proc newQueryOptions*(): QueryOptions = - return QueryOptions(select: @["documents.id AS id", "documents.data AS data", "content_type", "binary", "searchable", "created", "modified"], single: false, limit: 0, offset: 0, orderby: "", tags: "", search: "", folder: "") + return QueryOptions(select: @["documents.id AS id", "documents.data AS data", "content_type", "binary", "searchable", "created", "modified"], single: false, limit: 0, offset: 0, orderby: "", tags: "", search: "", folder: "", jsonFilter: "", tables: newSeq[string]())
M lib/utils.nimlib/utils.nim

@@ -52,16 +52,21 @@ innerSelect = innerSelect & "LIMIT " & $options.limit

if options.offset > 0: innerSelect = innerSelect & " OFFSET " & $options.offset result = result & options.select.join(", ") - result = result & " FROM documents JOIN (" & innerSelect & ") as ranktable USING(docid) JOIN searchdata USING(docid) " + options.tables = options.tables & @["documents"] + result = result & options.select.join(", ") + result = result & " FROM " & options.tables.join(", ") & " JOIN (" & innerSelect & ") as ranktable USING(docid) JOIN searchdata USING(docid) " result = result & "WHERE 1=1 " else: + options.tables = options.tables & @["searchdata"] result = result & options.select.join(", ") - result = result & " FROM searchdata " + result = result & " FROM "&options.tables.join(", ")&" " result = result & "WHERE 1=1 " options.orderby = "" else: + if not options.tables.contains "documents": + options.tables = options.tables & @["documents"] result = result & options.select.join(", ") - result = result & " FROM documents WHERE 1=1 " + result = result & " FROM "&options.tables.join(", ")&" WHERE 1=1 " if options.single: result = result & "AND id = ?" var doc_id_col: string

@@ -73,7 +78,12 @@ doc_id_col = "id"

if options.folder.len > 0: result = result & "AND " & doc_id_col & " LIKE ? " if options.tags.len > 0: + if options.jsonFilter.len > 0: + if options.tags.contains("$subtype:json"): + options.tags = options.tags & ",$subtype:json" result = result & options.tags.selectDocumentsByTags(doc_id_col) + if options.jsonFilter.len > 0: + result = result & "AND " & options.jsonFilter if options.search.len > 0: result = result & "AND searchdata MATCH '" & options.search.replace("'", "''") & "' " if options.orderby.len > 0 and options.select[0] != "COUNT(docid)":