Comparing version 1.0.41 to 1.0.43
@@ -6,5 +6,7 @@ declare type type_string = 'Object' | 'Number' | 'Boolean' | 'String' | 'Null' | 'Array' | 'RegExp' | 'Function' | 'Undefined'; | ||
export declare const deep_set: (path_array: (string | number)[], value: any, obj: any) => void; | ||
export declare const is_simple_object: (val: any) => boolean; | ||
export declare const deep_get: (path_array: (string | number)[], obj: any, default_value?: any) => any; | ||
/** | ||
* Like a map, but works on deeply nested objects and arrays. Processor function is run from the most deeply nested keys to the least deeply nested ones | ||
* Like a map, but works on deeply nested objects and arrays. Processor function runs on a depth first search, i.e. | ||
* the processor will only be called on an element after it has been called on all the children. | ||
* @param item can be an object or array | ||
@@ -23,3 +25,4 @@ * @param processor this function will run on every object, array and primitive value found | ||
export declare const deep_for_each: (item: any, processor: (value: any, path: (string | number)[]) => void, current_path?: any[]) => void; | ||
export declare const get_lower_paths: (item: Record<any, any> | any[], path: (string | number)[]) => any[][]; | ||
export declare const clone: (obj: any) => any; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.clone = exports.deep_for_each = exports.deep_map = exports.deep_get = exports.deep_set = exports.last = exports.drop = exports.type = void 0; | ||
exports.clone = exports.get_lower_paths = exports.deep_for_each = exports.deep_map = exports.deep_get = exports.is_simple_object = exports.deep_set = exports.last = exports.drop = exports.type = void 0; | ||
const type = (value) => { | ||
@@ -29,3 +29,3 @@ return value === null | ||
const is_array = Array.isArray(pointer); | ||
const is_object = exports.type(pointer) === 'Object'; | ||
const is_object = exports.is_simple_object(pointer); | ||
// if (is_array && type(path_el) !== 'Number') { | ||
@@ -44,4 +44,3 @@ // throw new Error('Trying to path into an array without a number index') | ||
} | ||
const child_type = exports.type(pointer[path_el]); | ||
const child_is_primitive = child_type !== 'Object' && child_type !== 'Array'; | ||
const child_is_primitive = !exports.is_simple_object(pointer[path_el]) && !Array.isArray(pointer[path_el]); | ||
if (!contains_path_el || child_is_primitive) { | ||
@@ -57,2 +56,5 @@ pointer[path_el] = next_el_default; | ||
exports.deep_set = deep_set; | ||
// from https://stackoverflow.com/a/16608074 | ||
const is_simple_object = val => (!!val) && (val.constructor === Object); | ||
exports.is_simple_object = is_simple_object; | ||
const deep_get = (path_array, obj, default_value = undefined) => { | ||
@@ -62,3 +64,3 @@ let pointer = obj; | ||
const is_array = Array.isArray(pointer); | ||
const is_object = exports.type(pointer) === 'Object'; | ||
const is_object = exports.is_simple_object(pointer); | ||
// if (is_array && type(path_el) !== 'Number') { | ||
@@ -82,3 +84,4 @@ // throw new Error('Trying to path into an array without a number index') | ||
/** | ||
* Like a map, but works on deeply nested objects and arrays. Processor function is run from the most deeply nested keys to the least deeply nested ones | ||
* Like a map, but works on deeply nested objects and arrays. Processor function runs on a depth first search, i.e. | ||
* the processor will only be called on an element after it has been called on all the children. | ||
* @param item can be an object or array | ||
@@ -97,3 +100,3 @@ * @param processor this function will run on every object, array and primitive value found | ||
} | ||
else if (typeof item === 'object') { | ||
else if (exports.is_simple_object(item)) { | ||
mapped_item = Object.keys(item).reduce((acc, key) => { | ||
@@ -123,4 +126,4 @@ const new_path = [...current_path, key]; | ||
const deep_for_each = (item, processor, current_path = []) => { | ||
const is_object = typeof item === 'object' && !Array.isArray(item) && item !== null && !(item instanceof Date); | ||
const is_array = typeof item === 'object' && Array.isArray(item); | ||
const is_object = exports.is_simple_object(item); | ||
const is_array = Array.isArray(item); | ||
const is_primitive = !is_object && !is_array; | ||
@@ -144,2 +147,9 @@ if (is_object) { | ||
exports.deep_for_each = deep_for_each; | ||
const get_lower_paths = (item, path) => { | ||
const keys = Array.isArray(item) | ||
? item.map((_, i) => [...path, i]) | ||
: Object.keys(item); | ||
return keys.map(key => [...path, key]); | ||
}; | ||
exports.get_lower_paths = get_lower_paths; | ||
/* | ||
@@ -146,0 +156,0 @@ From https://github.com/angus-c/just/blob/master/packages/collection-clone/index.js |
export declare const orma_introspect: (db: string, fn: (s: string[]) => Promise<Record<string, unknown>[][]>) => Promise<import("./introspector/introspector").orma_schema>; | ||
export declare const orma_query: (raw_query: any, orma_schema: any, query_function: (sql_string: string[]) => Promise<Record<string, unknown>[][]>) => Promise<{}>; | ||
export declare const orma_query: (raw_query: any, orma_schema: any, query_function: (sql_string: string[]) => Promise<Record<string, unknown>[][]>, escaping_function: (value: any) => any) => Promise<{}>; | ||
export declare const orma_mutate: (mutation: any, mutate_fn: import("./mutate/mutate").mutate_fn, escape_fn: import("./mutate/mutate").escape_fn, orma_schema: import("./introspector/introspector").orma_schema) => Promise<any>; |
@@ -9,3 +9,3 @@ "use strict"; | ||
const toposort_1 = require("../helpers/toposort"); | ||
const query_1 = require("../query/query"); | ||
const json_sql_1 = require("../query/json_sql"); | ||
const orma_mutate = async (mutation, mutate_fn, escape_fn, orma_schema) => { | ||
@@ -95,3 +95,3 @@ // [[{"operation":"create","paths":[...]]}],[{"operation":"create","paths":[...]}]] | ||
.filter(key => !identifying_keys.includes(key)) | ||
.filter(key => typeof record[key] !== 'object' || record[key] instanceof Date) | ||
.filter(key => !helpers_1.is_simple_object(record[key]) && !Array.isArray(record[key])) | ||
.filter(key => !schema_helpers_1.is_reserved_keyword(key)); | ||
@@ -111,3 +111,3 @@ return { | ||
command_json_escaped: update_ast_escaped, | ||
command_sql: query_1.json_to_sql(update_ast_escaped) | ||
command_sql: json_sql_1.json_to_sql(update_ast_escaped) | ||
}; | ||
@@ -136,3 +136,3 @@ }); | ||
command_json_escaped: el, | ||
command_sql: query_1.json_to_sql(el) | ||
command_sql: json_sql_1.json_to_sql(el) | ||
})); | ||
@@ -151,3 +151,3 @@ }; | ||
const keys_to_insert = Object.keys(record) | ||
.filter(key => typeof record[key] !== 'object' || record[key] instanceof Date) | ||
.filter(key => !helpers_1.is_simple_object(record[key]) && !Array.isArray(record[key])) | ||
.filter(key => !schema_helpers_1.is_reserved_keyword(key)); | ||
@@ -173,3 +173,3 @@ keys_to_insert.forEach(key => acc.add(key)); | ||
command_json_escaped, | ||
command_sql: query_1.json_to_sql(command_json_escaped) | ||
command_sql: json_sql_1.json_to_sql(command_json_escaped) | ||
} | ||
@@ -324,7 +324,4 @@ ]; | ||
var _a; | ||
if (typeof value !== 'object' || | ||
Array.isArray(value) || | ||
path.length === 0 || | ||
value === null || | ||
value instanceof Date) { | ||
if (!helpers_1.is_simple_object(value) || | ||
path.length === 0) { | ||
return; // not pointing to a single row | ||
@@ -331,0 +328,0 @@ } |
@@ -1,47 +0,1 @@ | ||
declare type select_expr = {}; | ||
declare type unary_function = { | ||
ascii: string | field; | ||
} | { | ||
bin: number | field; | ||
} | { | ||
bit_length: string | field; | ||
} | { | ||
char: (number | field)[]; | ||
} | { | ||
character_length: string | field; | ||
} | { | ||
concat: (string | field)[]; | ||
} | { | ||
concat_ws: (string | field)[]; | ||
}; | ||
declare type string_like = string | Date | number | { | ||
field: string; | ||
} | { | ||
ascii: string_like; | ||
} | { | ||
bin: number_like; | ||
} | { | ||
char: number_like[]; | ||
} | { | ||
character_length: string_like; | ||
} | { | ||
concat: string_like[]; | ||
} | { | ||
concat_ws: string_like[]; | ||
} | { | ||
elt: string_like[]; | ||
} | {}; | ||
declare type number_like = string | Date | number | { | ||
field: string; | ||
} | {}; | ||
declare type literal = string_literal | number | Date | { | ||
hex: string; | ||
} | { | ||
bit: string; | ||
} | boolean | null; | ||
declare type string_literal = string | { | ||
field: string; | ||
}; | ||
declare type field = { | ||
field: string; | ||
}; | ||
export {}; |
@@ -1,4 +0,46 @@ | ||
// interface query { | ||
// meta: query_meta | ||
// [entity_name: string]: query | query_meta | ||
// } | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const test_schema = { | ||
products: { | ||
id: {}, | ||
vendor_id: { | ||
references: { | ||
vendors: { | ||
id: {} | ||
} | ||
} | ||
} | ||
}, | ||
vendors: { | ||
id: {} | ||
}, | ||
images: { | ||
id: {}, | ||
product_id: { | ||
references: { | ||
products: { | ||
id: {} | ||
} | ||
} | ||
} | ||
}, | ||
image_urls: { | ||
image_id: { | ||
references: { | ||
images: { | ||
id: {} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
// test: can only put existing entities | ||
var obj = {}; | ||
obj = { products: {}, vendors: {} }; // good | ||
// @ts-expect-error | ||
obj = { a_fake_entity: {} }; // bad | ||
// test: can only nest connected entities | ||
obj = { products: { vendors: {} } }; // good | ||
obj = { products: { images: {} } }; // good | ||
// @ ts-expect-error | ||
obj = { products: { image_urls: {} } }; // bad |
import { orma_schema } from '../introspector/introspector'; | ||
declare type expression = { | ||
[commands: string]: expression | expression[]; | ||
} | primitive; | ||
declare type primitive = string | number | Date | Array<any>; | ||
export declare const json_to_sql: (expression: expression, path?: any[]) => any; | ||
export declare const get_query_plan: (query: any) => string[][][]; | ||
export declare const is_subquery: (subquery: any) => boolean; | ||
export declare const get_real_parent_name: (path: (string | number)[], query: any) => any; | ||
export declare const get_real_entity_name: (path: (string | number)[], query: any) => any; | ||
export declare const get_subquery_sql: (query: any, subquery_path: string[], previous_results: (string[] | Record<string, unknown>[])[][], orma_schema: orma_schema) => string; | ||
/** | ||
* transforms a query into a simplified json sql. This is still json, but can be parsed directly into sql (so no subqueries, $from is always there etc.) | ||
*/ | ||
export declare const query_to_json_sql: (query: any, subquery_path: string[], previous_results: (string[] | Record<string, unknown>[])[][], orma_schema: orma_schema) => Record<string, any>; | ||
export declare const select_to_json_sql: (query: any, subquery_path: string[], orma_schema: orma_schema) => any[]; | ||
export declare const where_to_json_sql: (query: any, subquery_path: string[], previous_results: (string[] | Record<string, unknown>[])[][], orma_schema: orma_schema) => any; | ||
export declare const having_to_json_sql: (query: any, subquery_path: string[], orma_schema: orma_schema) => any; | ||
export declare const combine_where_clauses: (where1: Record<string, unknown>, where2: Record<string, unknown>, connective: '$and' | '$or') => Record<string, unknown>; | ||
/** | ||
* The first argument to the $any_path macro is a list of connected entities, with the | ||
* first one being connected to the currently scoped entity. The second argument is a where clause. This will be scoped to the last table in the first argument. | ||
* This will then filter all the current entities, where there is at least one connected current_entity -> entity1 -> entity2 that matches the provided where clause | ||
* | ||
* @example | ||
* { | ||
* $where: { | ||
* $any_path: [['entity1', 'entity2'], { | ||
* ...where_clause_on_entity2 | ||
* }] | ||
* } | ||
* } | ||
* | ||
* @param where a where clause | ||
* @param current_entity the root entity, since the path in the $any_path clause only starts from subsequent tables | ||
* @param is_having if true, will use $having. Otherwise will use $where | ||
* @returns a modified where clause | ||
*/ | ||
export declare const convert_any_path_macro: (where: any, root_entity: string, is_having: boolean, orma_schema: orma_schema) => any; | ||
export declare const orma_nester: (results: [string[], Record<string, unknown>[]][], orma_schema: orma_schema) => {}; | ||
export declare const orma_query: (raw_query: any, orma_schema: any, query_function: (sql_string: string[]) => Promise<Record<string, unknown>[][]>) => Promise<{}>; | ||
export {}; | ||
export declare const orma_query: (raw_query: any, orma_schema: any, query_function: (sql_string: string[]) => Promise<Record<string, unknown>[][]>, escaping_function: (value: any) => any) => Promise<{}>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.orma_query = exports.orma_nester = exports.convert_any_path_macro = exports.combine_where_clauses = exports.having_to_json_sql = exports.where_to_json_sql = exports.select_to_json_sql = exports.query_to_json_sql = exports.get_subquery_sql = exports.get_real_entity_name = exports.get_real_parent_name = exports.is_subquery = exports.get_query_plan = exports.json_to_sql = void 0; | ||
exports.orma_query = exports.orma_nester = exports.having_to_json_sql = exports.get_real_entity_name = exports.get_real_parent_name = void 0; | ||
const helpers_1 = require("../helpers/helpers"); | ||
const nester_1 = require("../helpers/nester"); | ||
const schema_helpers_1 = require("../helpers/schema_helpers"); | ||
// this is a simple parser function, whatever comes in the json object is placed as-is in the output sql string. | ||
// more complicated rules (such as adding a 'from' clause, or adding group-by columns on select *) is handled while the query is still a json object | ||
const json_to_sql = (expression, path = []) => { | ||
// strings and other non-objects are returned as-is | ||
const is_object = typeof expression === 'object' && !Array.isArray(expression); | ||
if (!is_object) { | ||
return expression; | ||
} | ||
const sorted_commands = Object.keys(expression).sort((command1, command2) => { | ||
// unspecified commands go at the beginning | ||
const i1 = command_order[command1] || -1; | ||
const i2 = command_order[command2] || -1; | ||
return i1 - i2; | ||
}); | ||
const parsed_commands = sorted_commands | ||
.map(command => { | ||
if (expression[command] === undefined) { | ||
return ''; | ||
} | ||
const command_parser = sql_command_parsers[command]; | ||
if (!command_parser) { | ||
throw new Error(`Cannot find command parser for ${command}.`); | ||
} | ||
const args = expression[command]; | ||
const parsed_args = Array.isArray(args) | ||
? args.map((arg, i) => exports.json_to_sql(arg, [...path, command, i])) | ||
: exports.json_to_sql(args, [...path, command]); | ||
return command_parser(parsed_args, path); | ||
}) | ||
.filter(el => el !== ''); | ||
return parsed_commands.join(' '); | ||
}; | ||
exports.json_to_sql = json_to_sql; | ||
const command_order_array = [ | ||
'$delete_from', | ||
'$update', | ||
'$set', | ||
'$select', | ||
'$from', | ||
'$where', | ||
'$group_by', | ||
'$having', | ||
'$order_by', | ||
'$limit', | ||
'$offset', | ||
'$insert_into', | ||
'$values' | ||
]; | ||
const command_order = command_order_array.reduce((acc, val, i) => { | ||
acc[val] = i; | ||
return acc; | ||
}, {}); | ||
/* | ||
{ | ||
$eq: ['variant_id', { | ||
$any: { | ||
$select... | ||
} | ||
}] | ||
} | ||
*/ | ||
const sql_command_parsers = { | ||
$select: args => `SELECT ${args.join(', ')}`, | ||
$as: args => `(${args[0]}) AS ${args[1]}`, | ||
$from: args => `FROM ${args}`, | ||
$where: args => `WHERE ${args}`, | ||
$having: args => `HAVING ${args}`, | ||
$in: (args, path) => `${args[0]}${helpers_1.last(path) === '$not' ? ' NOT' : ''} IN (${args[1]})`, | ||
$group_by: args => `GROUP BY ${args.join(', ')}`, | ||
$order_by: args => `ORDER BY ${args.join(', ')}`, | ||
$asc: args => `${args} ASC`, | ||
$desc: args => `${args} DESC`, | ||
$and: (args, path) => { | ||
const res = `(${args.join(') AND (')})`; | ||
return helpers_1.last(path) === '$not' ? `NOT (${res})` : res; | ||
}, | ||
$or: (args, path) => { | ||
const res = `(${args.join(') OR (')})`; | ||
return helpers_1.last(path) === '$not' ? `NOT (${res})` : res; | ||
}, | ||
$any: args => `ANY (${args})`, | ||
$all: args => `ALL (${args})`, | ||
$eq: (args, path) => args[1] === null | ||
? `${args[0]}${helpers_1.last(path) === '$not' ? ' NOT' : ''} IS NULL` | ||
: `${args[0]} ${helpers_1.last(path) === '$not' ? '!' : ''}= ${args[1]}`, | ||
$gt: (args, path) => `${args[0]} ${helpers_1.last(path) === '$not' ? '<=' : '>'} ${args[1]}`, | ||
$lt: (args, path) => `${args[0]} ${helpers_1.last(path) === '$not' ? '>=' : '<'} ${args[1]}`, | ||
$gte: (args, path) => `${args[0]} ${helpers_1.last(path) === '$not' ? '<' : '>='} ${args[1]}`, | ||
$lte: (args, path) => `${args[0]} ${helpers_1.last(path) === '$not' ? '>' : '<='} ${args[1]}`, | ||
$exists: (args, path) => `${helpers_1.last(path) === '$not' ? 'NOT ' : ''}EXISTS (${args})`, | ||
$limit: args => `LIMIT ${args}`, | ||
$offset: args => `OFFSET ${args}`, | ||
$like: (args, path) => { | ||
const string_arg = args[1].toString(); | ||
const search_value = string_arg.replace(/^\'/, '').replace(/\'$/, ''); // get rid of quotes if they were put there by escape() | ||
return `${args[0]}${helpers_1.last(path) === '$not' ? ' NOT' : ''} LIKE '%${search_value}%'`; | ||
}, | ||
$not: args => args, | ||
$sum: args => `SUM(${args})`, | ||
// not: { | ||
// in: args => `${args[0]} NOT IN (${args[1]})`, | ||
// and: args => `NOT ((${args.join(') AND (')}))`, | ||
// or: args => `NOT ((${args.join(') OR (')}))`, | ||
// eq: args => args[1] === null ? `${args[0]} IS NOT NULL` : `${args[0]} != ${args[1]}`, | ||
// gt: args => `${args[0]} <= ${args[1]}`, | ||
// lt: args => `${args[0]} >= ${args[1]}`, | ||
// gte: args => `${args[0]} < ${args[1]}`, | ||
// lte: args => `${args[0]} > ${args[1]}`, | ||
// exists: args => `NOT EXISTS (${args})`, | ||
// } | ||
$insert_into: ([table_name, [...columns]]) => `INSERT INTO ${table_name} (${columns.join(', ')})`, | ||
$values: (values) => `VALUES ${values.map(inner_values => `(${inner_values.join(', ')})`).join(', ')}`, | ||
$update: table_name => `UPDATE ${table_name}`, | ||
$set: (items) => `SET ${items.map(([column, value]) => `${column} = ${value}`).join(', ')}`, | ||
$delete_from: table_name => `DELETE FROM ${table_name}` | ||
}; | ||
/* | ||
[ | ||
[{ | ||
sql: 'SELECT' | ||
}] | ||
] | ||
*/ | ||
const get_query_plan = (query) => { | ||
const query_plan = []; | ||
helpers_1.deep_for_each(query, (value, path) => { | ||
if (typeof value === 'object') { | ||
const query = value; | ||
const has_filter = value.$where || value.$having; | ||
const child_paths = Object.keys(value) | ||
.map(child_key => { | ||
if (exports.is_subquery(value[child_key])) { | ||
return [...path, child_key]; | ||
} | ||
}) | ||
.filter(el => el !== undefined); | ||
if (child_paths.length === 0) { | ||
return; // subquery with nothing else nested on | ||
} | ||
const is_root = path.length === 0; // this is to start off the query plan (cant append if there is nothing there) | ||
// entities directly under the root need to ge after because all entitiesneed at least one ancestor which is already queried | ||
// so we can search for those ancestor ids. This ensures that at least the root will have already been searched. | ||
const is_root_child = path.length === 1; | ||
// if this has a $where, or its the root/root child, then all the child paths go to a new tier. Otherwise, the child paths are appended on to the last tier | ||
if (has_filter || is_root || is_root_child) { | ||
query_plan.push(child_paths); | ||
} | ||
else { | ||
helpers_1.last(query_plan).push(...child_paths); | ||
} | ||
} | ||
}); | ||
return query_plan; | ||
}; | ||
exports.get_query_plan = get_query_plan; | ||
const is_subquery = (subquery) => { | ||
if (typeof subquery !== 'object' || Array.isArray(subquery)) { | ||
return false; | ||
} | ||
const subquery_keys = Object.keys(subquery); | ||
return subquery_keys.some(key => !schema_helpers_1.is_reserved_keyword(key)) || subquery_keys.length === 0; | ||
}; | ||
exports.is_subquery = is_subquery; | ||
const json_sql_1 = require("./json_sql"); | ||
const any_path_macro_1 = require("./macros/any_path_macro"); | ||
const escaping_macros_1 = require("./macros/escaping_macros"); | ||
const nesting_macro_1 = require("./macros/nesting_macro"); | ||
const select_macro_1 = require("./macros/select_macro"); | ||
const query_plan_1 = require("./query_plan"); | ||
// This function will default to the from clause | ||
@@ -187,88 +25,44 @@ const get_real_parent_name = (path, query) => { | ||
exports.get_real_entity_name = get_real_entity_name; | ||
const get_subquery_sql = (query, subquery_path, previous_results, orma_schema) => { | ||
const json_sql = exports.query_to_json_sql(query, subquery_path, previous_results, orma_schema); | ||
const sql = exports.json_to_sql(json_sql); | ||
return sql; | ||
}; | ||
exports.get_subquery_sql = get_subquery_sql; | ||
/** | ||
* transforms a query into a simplified json sql. This is still json, but can be parsed directly into sql (so no subqueries, $from is always there etc.) | ||
*/ | ||
const query_to_json_sql = (query, subquery_path, previous_results, orma_schema) => { | ||
var _a; | ||
const subquery = helpers_1.deep_get(subquery_path, query); | ||
const reserved_commands = Object.keys(subquery).filter(schema_helpers_1.is_reserved_keyword); | ||
const reserved_json = reserved_commands.reduce((previous, key) => { | ||
return Object.assign(Object.assign({}, previous), { [key]: subquery[key] }); | ||
}, {}); | ||
const $select = exports.select_to_json_sql(query, subquery_path, orma_schema); | ||
const $from = (_a = subquery.$from) !== null && _a !== void 0 ? _a : helpers_1.last(subquery_path); | ||
const $where = exports.where_to_json_sql(query, subquery_path, previous_results, orma_schema); | ||
const $having = exports.having_to_json_sql(query, subquery_path, orma_schema); | ||
const json_sql = Object.assign({}, reserved_json); | ||
if ($select) { | ||
json_sql.$select = $select; | ||
} | ||
if ($from) { | ||
json_sql.$from = $from; | ||
} | ||
if ($where) { | ||
json_sql.$where = $where; | ||
} | ||
if ($having) { | ||
json_sql.$having = $having; | ||
} | ||
return json_sql; | ||
}; | ||
exports.query_to_json_sql = query_to_json_sql; | ||
const select_to_json_sql = (query, subquery_path, orma_schema) => { | ||
const subquery = helpers_1.deep_get(subquery_path, query); | ||
const entity_name = helpers_1.last(subquery_path); | ||
const $select = Object.keys(subquery).flatMap(key => { | ||
var _a; | ||
if (schema_helpers_1.is_reserved_keyword(key)) { | ||
return []; | ||
} | ||
if (subquery[key] === true) { | ||
return key; | ||
} | ||
if (typeof subquery[key] === 'string') { | ||
return { $as: [subquery[key], key] }; | ||
} | ||
if (typeof subquery[key] === 'object' && !exports.is_subquery(subquery[key])) { | ||
return { $as: [subquery[key], key] }; | ||
} | ||
if (typeof subquery[key] === 'object' && exports.is_subquery(subquery[key])) { | ||
const lower_subquery = subquery[key]; | ||
const lower_subquery_entity = (_a = lower_subquery.$from) !== null && _a !== void 0 ? _a : key; | ||
const edge_to_lower_table = schema_helpers_1.get_direct_edge(entity_name, lower_subquery_entity, orma_schema); | ||
return edge_to_lower_table.from_field; | ||
} | ||
return []; // subqueries are not handled here | ||
}); | ||
if (subquery_path.length > 1) { | ||
const higher_entity = subquery_path[subquery_path.length - 2]; | ||
const edge_to_higher_entity = schema_helpers_1.get_direct_edge(entity_name, higher_entity, orma_schema); | ||
$select.push(edge_to_higher_entity.from_field); | ||
} | ||
return [...new Set($select)]; // unique values | ||
}; | ||
exports.select_to_json_sql = select_to_json_sql; | ||
const where_to_json_sql = (query, subquery_path, previous_results, orma_schema) => { | ||
const subquery = helpers_1.deep_get(subquery_path, query); | ||
let $where = subquery.$where; | ||
const is_root_subquery = subquery_path.length <= 1; | ||
if (!is_root_subquery) { | ||
const nesting_ancestor_index = get_nesting_ancestor_index(query, subquery_path); | ||
const ancestor_path = subquery_path.slice(0, nesting_ancestor_index + 1); | ||
const ancestor_rows = previous_results | ||
.filter(previous_result => previous_result[0].toString() === ancestor_path.toString()) | ||
.map(previous_result => previous_result[1])[0]; | ||
const path_to_ancestor = subquery_path.slice(nesting_ancestor_index, Infinity).reverse(); | ||
const ancestor_where_clause = get_ancestor_where_clause(ancestor_rows, path_to_ancestor, orma_schema); | ||
$where = exports.combine_where_clauses($where, ancestor_where_clause, '$and'); | ||
} | ||
return $where; | ||
}; | ||
exports.where_to_json_sql = where_to_json_sql; | ||
// export const get_subquery_sql = ( | ||
// query, | ||
// subquery_path: string[], | ||
// previous_results: (string[] | Record<string, unknown>[])[][], | ||
// orma_schema: orma_schema | ||
// ): string => { | ||
// const json_sql = query_to_json_sql(query, subquery_path, previous_results, orma_schema) | ||
// const sql = json_to_sql(json_sql) | ||
// return sql | ||
// } | ||
// /** | ||
// * transforms a query into a simplified json sql. This is still json, but can be parsed directly into sql (so no subqueries, $from is always there etc.) | ||
// */ | ||
// export const query_to_json_sql = ( | ||
// query, | ||
// subquery_path: string[], | ||
// previous_results: (string[] | Record<string, unknown>[])[][], | ||
// orma_schema: orma_schema | ||
// ): Record<string, any> => { | ||
// const subquery = deep_get(subquery_path, query) | ||
// // strip sub subqueries from the subquery | ||
// const reserved_commands = Object.keys(subquery).filter(is_reserved_keyword) | ||
// const reserved_json = reserved_commands.reduce((previous, key) => { | ||
// return { | ||
// ...previous, | ||
// [key]: subquery[key] | ||
// } | ||
// }, {}) | ||
// // | ||
// const $select = select_to_json_sql(query, subquery_path, orma_schema) | ||
// const $from = subquery.$from ?? last(subquery_path) | ||
// const $where = where_to_json_sql(query, subquery_path, previous_results, orma_schema) | ||
// const $having = having_to_json_sql(query, subquery_path, orma_schema) | ||
// const json_sql: Record<string, unknown> = { | ||
// ...reserved_json, | ||
// ...($select && { $select }), | ||
// ...($from && { $from }), | ||
// ...($where && { $where }), | ||
// ...($having && { $having }) | ||
// } | ||
// return json_sql | ||
// } | ||
const having_to_json_sql = (query, subquery_path, orma_schema) => { | ||
@@ -280,142 +74,2 @@ const subquery = helpers_1.deep_get(subquery_path, query); | ||
exports.having_to_json_sql = having_to_json_sql; | ||
/* gives a query that contains both queries combined with the either '$and' or '$or'. | ||
* Prevents combine_with duplication if one of the qureies, for instance, already has an '$and' clause | ||
*/ | ||
const combine_where_clauses = (where1, where2, connective) => { | ||
if (!where1) { | ||
return where2; | ||
} | ||
if (!where2) { | ||
return where1; | ||
} | ||
if (where1[connective] && where2[connective]) { | ||
const where1_items = where1[connective]; | ||
const where2_items = where2[connective]; | ||
return { | ||
[connective]: [...where1_items, ...where2_items] | ||
}; | ||
} | ||
if (where1[connective]) { | ||
const where1_items = where1[connective]; | ||
return { | ||
[connective]: [...where1_items, where2] | ||
}; | ||
} | ||
if (where2[connective]) { | ||
const where2_items = where2[connective]; | ||
return { | ||
[connective]: [where1, ...where2_items] | ||
}; | ||
} | ||
return { | ||
[connective]: [where1, where2] | ||
}; | ||
}; | ||
exports.combine_where_clauses = combine_where_clauses; | ||
/** | ||
* The first argument to the $any_path macro is a list of connected entities, with the | ||
* first one being connected to the currently scoped entity. The second argument is a where clause. This will be scoped to the last table in the first argument. | ||
* This will then filter all the current entities, where there is at least one connected current_entity -> entity1 -> entity2 that matches the provided where clause | ||
* | ||
* @example | ||
* { | ||
* $where: { | ||
* $any_path: [['entity1', 'entity2'], { | ||
* ...where_clause_on_entity2 | ||
* }] | ||
* } | ||
* } | ||
* | ||
* @param where a where clause | ||
* @param current_entity the root entity, since the path in the $any_path clause only starts from subsequent tables | ||
* @param is_having if true, will use $having. Otherwise will use $where | ||
* @returns a modified where clause | ||
*/ | ||
const convert_any_path_macro = (where, root_entity, is_having, orma_schema) => { | ||
if (!where) { | ||
return where; | ||
} | ||
const processor = (value, path) => { | ||
var _a; | ||
if (typeof value === 'object' && value.$any) { | ||
const [any_path, subquery] = value.$any; | ||
const previous_entities = path.flatMap((path_el, i) => { | ||
if (path_el === '$any') { | ||
const path_segment = path.slice(0, i + 1); | ||
const previous_any = helpers_1.deep_get(path_segment, where); | ||
return helpers_1.last(previous_any[0]); | ||
} | ||
else { | ||
return []; | ||
} | ||
}); | ||
const current_entity = (_a = helpers_1.last(previous_entities)) !== null && _a !== void 0 ? _a : root_entity; | ||
const full_path = [current_entity].concat(any_path); | ||
const edge_path = schema_helpers_1.get_edge_path(full_path, orma_schema).reverse(); | ||
const query = edge_path.reduce((acc, edge) => { | ||
return { | ||
$in: [ | ||
edge.from_field, | ||
{ | ||
$select: [edge.to_field], | ||
$from: edge.to_entity, | ||
[is_having ? '$having' : '$where']: acc | ||
} | ||
] | ||
}; | ||
}, subquery); | ||
return query; | ||
} | ||
else { | ||
return value; | ||
} | ||
}; | ||
return helpers_1.deep_map(where, processor); | ||
}; | ||
exports.convert_any_path_macro = convert_any_path_macro; | ||
/** | ||
* Gets the closest ancestor that satisfies either of two conditions: | ||
* 1. has a where or having clause | ||
* 2. is the root ancestor | ||
* | ||
* These are the ancestors that will be split into sequential queries, so we do a server-side nesting for them, | ||
* rather than duplicating these queries in the database | ||
* | ||
* @returns The index in the subquery_path of the nesting ancestor | ||
*/ | ||
const get_nesting_ancestor_index = (query, subquery_path) => { | ||
for (let i = subquery_path.length - 1; i >= 0; i--) { | ||
const subpath = subquery_path.slice(0, i + 1); | ||
const subquery = helpers_1.deep_get(subpath, query); | ||
if (subquery.$where || subquery.$having) { | ||
return i; | ||
} | ||
} | ||
return 0; | ||
}; | ||
/** | ||
* Generates a where clause that restricts rows to only be ones connected to a given ancestor through a given route | ||
* @param ancestor_rows The foreign key values in these will be inserted into the where clause | ||
* @param path_to_ancestor This should start with the current table and end with the ancestor table | ||
* @returns A where clause | ||
*/ | ||
const get_ancestor_where_clause = (ancestor_rows, path_to_ancestor, orma_schema) => { | ||
const ancestor_name = helpers_1.last(path_to_ancestor); | ||
const table_under_ancestor = path_to_ancestor[path_to_ancestor.length - 2]; | ||
const last_edge_to_ancestor = schema_helpers_1.get_direct_edge(table_under_ancestor, ancestor_name, orma_schema); | ||
if (ancestor_rows === undefined || ancestor_rows.length === 0) { | ||
throw Error(`No ancestor rows provided for ${ancestor_name}`); | ||
} | ||
const ancestor_linking_key_values = ancestor_rows.map(row => row[last_edge_to_ancestor.to_field]); | ||
const any_path = path_to_ancestor.slice(1, path_to_ancestor.length - 1); | ||
const ancestor_query = exports.convert_any_path_macro({ | ||
$any: [ | ||
any_path, | ||
{ | ||
$in: [last_edge_to_ancestor.from_field, ancestor_linking_key_values] | ||
} | ||
] | ||
}, path_to_ancestor[0], false, orma_schema); | ||
return ancestor_query; | ||
}; | ||
const orma_nester = (results, orma_schema) => { | ||
@@ -442,18 +96,17 @@ // get data in the right format for the nester | ||
// export const orma_query = async <schema>(raw_query: validate_query<schema>, orma_schema: validate_orma_schema<schema>, query_function: (sql_string: string) => Promise<Record<string, unknown>[]>) => { | ||
const orma_query = async (raw_query, orma_schema, query_function) => { | ||
const query = helpers_1.clone(raw_query); // clone query so we can apply macros without mutating user input | ||
const query_plan = exports.get_query_plan(query); | ||
const orma_query = async (raw_query, orma_schema, query_function, escaping_function) => { | ||
const query = helpers_1.clone(raw_query); // clone query so we can apply macros without mutating the actual input query | ||
escaping_macros_1.apply_field_macro(query); | ||
any_path_macro_1.apply_any_path_macro(query, orma_schema); | ||
select_macro_1.apply_select_macro(query, orma_schema); | ||
const query_plan = query_plan_1.get_query_plan(query); | ||
let results = []; | ||
// Sequential for query plan | ||
for (let i = 0; i < query_plan.length; i++) { | ||
const paths = query_plan[i]; | ||
for (const paths of query_plan) { | ||
const sql_strings = paths.map(path => { | ||
// apply macros | ||
const where = helpers_1.deep_get([...path, '$where'], query); | ||
const macrod_where = exports.convert_any_path_macro(where, helpers_1.last(path), false, orma_schema); | ||
helpers_1.deep_set([...path, '$where'], macrod_where, query); | ||
const having = helpers_1.deep_get([...path, '$having'], query); | ||
const macrod_having = exports.convert_any_path_macro(having, helpers_1.last(path), false, orma_schema); | ||
helpers_1.deep_set([...path, '$having'], macrod_having, query); | ||
return exports.get_subquery_sql(query, path, results, orma_schema); | ||
// the nesting macro needs previous results, so we cant do it in the beginning | ||
nesting_macro_1.apply_nesting_macro(query, path, results, orma_schema); | ||
const subquery = helpers_1.deep_get(path, query); | ||
escaping_macros_1.apply_escaping_macro(subquery, (value, path) => escaping_function(value)); | ||
return json_sql_1.json_to_sql(subquery); | ||
}); | ||
@@ -469,53 +122,1 @@ // Promise.all for each element in query plan | ||
exports.orma_query = orma_query; | ||
// const test = orma_query({}, { | ||
// products: { id: {} }, | ||
// variants: { | ||
// id: {}, | ||
// product_id: { | ||
// // references: { products: { id: {} } } | ||
// references: { products: {id: {}} } | ||
// } | ||
// }, | ||
// images: { | ||
// id: {}, | ||
// variant_id: { | ||
// references: { variants: { id8: {} }, hi: {} } | ||
// } | ||
// }, | ||
// images2: { | ||
// id: {}, | ||
// variant_id: { | ||
// // a@ts-expect-error | ||
// references: { oops: { id: {} } } | ||
// } | ||
// }, | ||
// images3: { | ||
// id: {}, | ||
// variant_id: { | ||
// // a@ts-expect-error | ||
// references: { variants: { id: {} }, hi: {}, } | ||
// } | ||
// } | ||
// }, (s) => ([{ a: 'hi' }])) | ||
// type validate_query<schema> = { | ||
// [entity in keyof schema]: boolean | ||
// } | ||
// const test2 = orma_query({ | ||
// variants: true, | ||
// products: true, | ||
// poop: true, | ||
// }, { | ||
// variants: {id: {}}, | ||
// products: {id: {}}, | ||
// }, (s) => Promise.resolve([{}])) | ||
/* | ||
- get all data | ||
- nest data | ||
*/ |
@@ -5,3 +5,2 @@ "use strict"; | ||
const mocha_1 = require("mocha"); | ||
const sql_formatter_1 = require("sql-formatter"); | ||
const query_1 = require("./query"); | ||
@@ -43,443 +42,2 @@ mocha_1.describe('query', () => { | ||
}; | ||
mocha_1.describe('json_to_sql', () => { | ||
mocha_1.test('joins commands', () => { | ||
const json = { | ||
$select: ['a'], | ||
$from: 'b' | ||
}; | ||
const sql = sql_formatter_1.format(query_1.json_to_sql(json)); | ||
const goal = sql_formatter_1.format(`SELECT a FROM b`); | ||
chai_1.expect(sql).to.equal(goal); | ||
}); | ||
mocha_1.test('nested command work', () => { | ||
const json = { | ||
$where: { | ||
$eq: ['a', 'b'] | ||
} | ||
}; | ||
const sql = sql_formatter_1.format(query_1.json_to_sql(json)); | ||
const goal = sql_formatter_1.format('WHERE a = b'); | ||
chai_1.expect(sql).to.equal(goal); | ||
}); | ||
mocha_1.test("'not' command works", () => { | ||
const json = { | ||
$not: { | ||
$in: ['a', [1, 2]] | ||
} | ||
}; | ||
const sql = sql_formatter_1.format(query_1.json_to_sql(json)); | ||
const goal = sql_formatter_1.format('a NOT IN (1, 2)'); | ||
chai_1.expect(sql).to.equal(goal); | ||
}); | ||
mocha_1.test('ignores undefined properties', () => { | ||
const json = { | ||
$having: undefined | ||
}; | ||
const sql = sql_formatter_1.format(query_1.json_to_sql(json)); | ||
const goal = sql_formatter_1.format(''); | ||
chai_1.expect(sql).to.equal(goal); | ||
}); | ||
}); | ||
mocha_1.describe(query_1.get_query_plan.name, () => { | ||
mocha_1.test('splits by $where clause and $having', () => { | ||
const query = { | ||
vendors: { | ||
products: { | ||
$where: { $eq: ['id', 0] }, | ||
vins: { | ||
id: true | ||
}, | ||
images: { | ||
image_urls: { | ||
$having: { $eq: ['id', 0] }, | ||
id: true | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
const result = query_1.get_query_plan(query); | ||
// the split happens at variants because it has a where clause | ||
const goal = [ | ||
[['vendors']], | ||
[['vendors', 'products']], | ||
[ | ||
['vendors', 'products', 'vins'], | ||
['vendors', 'products', 'images'], | ||
['vendors', 'products', 'images', 'image_urls'] | ||
] // these are queried concurrently | ||
]; | ||
chai_1.expect(result).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('handles multiple top level props', () => { | ||
const query = { | ||
vendors: { | ||
id: true | ||
}, | ||
products: { | ||
id: true | ||
} | ||
}; | ||
const result = query_1.get_query_plan(query); | ||
const goal = [[['vendors'], ['products']]]; | ||
chai_1.expect(result).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('handles renamed queries', () => { | ||
const query = { | ||
my_products: { | ||
$from: 'products', | ||
id: true | ||
} | ||
}; | ||
const result = query_1.get_query_plan(query); | ||
const goal = [[['my_products']]]; | ||
chai_1.expect(result).to.deep.equal(goal); | ||
}); | ||
}); | ||
mocha_1.describe('is_subquery', () => { | ||
mocha_1.test('is subquery', () => { | ||
const result = query_1.is_subquery({ | ||
$from: 'products', | ||
id: {} | ||
}); | ||
chai_1.expect(result).to.equal(true); | ||
}); | ||
mocha_1.test('not subquery', () => { | ||
const result = query_1.is_subquery({ | ||
$from: 'products' | ||
}); | ||
chai_1.expect(result).to.equal(false); | ||
}); | ||
}); | ||
mocha_1.describe('convert_any_clauses', () => { | ||
mocha_1.test('multiple any clauses', () => { | ||
const where = { | ||
$and: [ | ||
{ | ||
$any: [ | ||
['images'], | ||
{ | ||
$eq: ['id', 1] | ||
} | ||
] | ||
}, | ||
{ | ||
$any: [ | ||
['vendors'], | ||
{ | ||
$eq: ['id', 1] | ||
} | ||
] | ||
} | ||
] | ||
}; | ||
const converted_where = query_1.convert_any_path_macro(where, 'products', false, orma_schema); | ||
const goal = { | ||
$and: [ | ||
{ | ||
$in: [ | ||
'id', | ||
{ | ||
$select: ['product_id'], | ||
$from: 'images', | ||
$where: { | ||
$eq: ['id', 1] | ||
} | ||
} | ||
] | ||
}, | ||
{ | ||
$in: [ | ||
'vendor_id', | ||
{ | ||
$select: ['id'], | ||
$from: 'vendors', | ||
$where: { | ||
$eq: ['id', 1] | ||
} | ||
} | ||
] | ||
} | ||
] | ||
}; | ||
chai_1.expect(converted_where).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('deep any path', () => { | ||
const where = { | ||
$any: [ | ||
['images', 'image_urls'], | ||
{ | ||
$eq: ['id', 1] | ||
} | ||
] | ||
}; | ||
const converted_where = query_1.convert_any_path_macro(where, 'products', false, orma_schema); | ||
const goal = { | ||
$in: [ | ||
'id', | ||
{ | ||
$select: ['product_id'], | ||
$from: 'images', | ||
$where: { | ||
$in: [ | ||
'id', | ||
{ | ||
$select: ['image_id'], | ||
$from: 'image_urls', | ||
$where: { | ||
$eq: ['id', 1] | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
}; | ||
chai_1.expect(converted_where).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('nested anys', () => { | ||
const where = { | ||
$any: [ | ||
['images'], | ||
{ | ||
$any: [ | ||
['image_urls'], | ||
{ | ||
$eq: ['id', 1] | ||
} | ||
] | ||
} | ||
] | ||
}; | ||
const converted_where = query_1.convert_any_path_macro(where, 'products', false, orma_schema); | ||
const goal = { | ||
$in: [ | ||
'id', | ||
{ | ||
$select: ['product_id'], | ||
$from: 'images', | ||
$where: { | ||
$in: [ | ||
'id', | ||
{ | ||
$select: ['image_id'], | ||
$from: 'image_urls', | ||
$where: { | ||
$eq: ['id', 1] | ||
} | ||
} | ||
] | ||
} | ||
} | ||
] | ||
}; | ||
chai_1.expect(converted_where).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('uses having', () => { | ||
const where = { | ||
$any: [ | ||
['images'], | ||
{ | ||
$eq: ['id', 1] | ||
} | ||
] | ||
}; | ||
const converted_where = query_1.convert_any_path_macro(where, 'products', true, orma_schema); | ||
const goal = { | ||
$in: [ | ||
'id', | ||
{ | ||
$select: ['product_id'], | ||
$from: 'images', | ||
$having: { | ||
$eq: ['id', 1] | ||
} | ||
} | ||
] | ||
}; | ||
chai_1.expect(converted_where).to.deep.equal(goal); | ||
}); | ||
}); | ||
mocha_1.describe('query_to_json_sql', () => { | ||
mocha_1.test('handles selects/handles root', () => { | ||
const query = { | ||
products: { | ||
id: true, | ||
my_title: 'title', | ||
total_quantity: { | ||
$sum: 'quantity' | ||
} | ||
} | ||
}; | ||
const json_sql = query_1.query_to_json_sql(query, ['products'], [], {}); | ||
const goal = { | ||
$select: [ | ||
'id', | ||
{ $as: ['title', 'my_title'] }, | ||
{ $as: [{ $sum: 'quantity' }, 'total_quantity'] } | ||
], | ||
$from: 'products' | ||
}; | ||
chai_1.expect(json_sql).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('handles root nesting', () => { | ||
const query = { | ||
products: { | ||
id: true, | ||
images: { | ||
id: true, | ||
product_id: true | ||
} | ||
} | ||
}; | ||
const previous_results = [[['products'], [{ id: 1 }, { id: 2 }]]]; | ||
const json_sql = query_1.query_to_json_sql(query, ['products', 'images'], previous_results, orma_schema); | ||
const goal = { | ||
$select: ['id', 'product_id'], | ||
$from: 'images', | ||
$where: { | ||
$in: ['product_id', [1, 2]] | ||
} | ||
}; | ||
chai_1.expect(json_sql).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('handles adding foreign keys', () => { | ||
var _a; | ||
const query = { | ||
products: { | ||
images: { url: true } | ||
} | ||
}; | ||
const previous_results = [[['products'], [{ id: 1 }, { id: 2 }]]]; | ||
const json_sql1 = query_1.query_to_json_sql(query, ['products'], previous_results, orma_schema); | ||
const goal1 = { | ||
$select: ['id'], | ||
$from: 'products' | ||
}; | ||
const json_sql2 = query_1.query_to_json_sql(query, ['products', 'images'], previous_results, orma_schema); | ||
(_a = json_sql2 === null || json_sql2 === void 0 ? void 0 : json_sql2.$select) === null || _a === void 0 ? void 0 : _a.sort(); | ||
const goal2 = { | ||
$select: ['product_id', 'url'].sort(), | ||
$from: 'images', | ||
$where: { | ||
$in: ['product_id', [1, 2]] | ||
} | ||
}; | ||
chai_1.expect(json_sql1).to.deep.equal(goal1); | ||
chai_1.expect(json_sql2).to.deep.equal(goal2); | ||
}); | ||
mocha_1.test('handles deep nesting', () => { | ||
var _a; | ||
const query = { | ||
products: { | ||
images: { | ||
image_urls: { | ||
id: true | ||
} | ||
} | ||
} | ||
}; | ||
const previous_results = [[['products'], [{ id: 1 }, { id: 2 }]]]; | ||
const json_sql = query_1.query_to_json_sql(query, ['products', 'images', 'image_urls'], previous_results, orma_schema); | ||
(_a = json_sql === null || json_sql === void 0 ? void 0 : json_sql.$select) === null || _a === void 0 ? void 0 : _a.sort(); | ||
const goal = { | ||
$select: ['image_id', 'id'].sort(), | ||
$from: 'image_urls', | ||
$where: { | ||
$in: [ | ||
'image_id', | ||
{ | ||
$select: ['id'], | ||
$from: 'images', | ||
$where: { | ||
$in: ['product_id', [1, 2]] | ||
} | ||
} | ||
] | ||
} | ||
}; | ||
chai_1.expect(json_sql).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('handles nesting under where clause', () => { | ||
const query = { | ||
products: { | ||
images: { | ||
$where: { $gt: ['id', 0] }, | ||
image_urls: {} | ||
} | ||
} | ||
}; | ||
const previous_results = [ | ||
[['products'], [{ id: 1 }, { id: 2 }]], | ||
[['products', 'images'], [{ id: 3 }]] | ||
]; | ||
const json_sql = query_1.query_to_json_sql(query, ['products', 'images', 'image_urls'], previous_results, orma_schema); | ||
const goal = { | ||
$select: ['image_id'], | ||
$from: 'image_urls', | ||
$where: { | ||
$in: ['image_id', [3]] | ||
} | ||
}; | ||
chai_1.expect(json_sql).to.deep.equal(goal); | ||
}); | ||
mocha_1.test('ignores undefined where/having clauses', () => { | ||
const query = { | ||
products: { | ||
images: { | ||
$where: undefined, | ||
$having: undefined, | ||
image_urls: {} | ||
} | ||
} | ||
}; | ||
const previous_results = [ | ||
[['products'], [{ id: 1 }, { id: 2 }]], | ||
[['products', 'images'], [{ id: 3 }]] | ||
]; | ||
const json_sql = query_1.query_to_json_sql(query, ['products', 'images', 'image_urls'], previous_results, orma_schema); | ||
const goal = { | ||
$select: ['image_id'], | ||
$from: 'image_urls', | ||
$where: { | ||
$in: [ | ||
'image_id', | ||
{ | ||
$select: ['id'], | ||
$from: 'images', | ||
$where: { | ||
$in: ['product_id', [1, 2]] | ||
} | ||
} | ||
] | ||
} | ||
}; | ||
chai_1.expect(json_sql).to.deep.equal(goal); | ||
}); | ||
mocha_1.test("respects 'from' clause", () => { | ||
const query = { | ||
my_products: { | ||
id: true, | ||
$from: 'products' | ||
} | ||
}; | ||
const json_sql = query_1.query_to_json_sql(query, ['my_products'], [], {}); | ||
const goal = { | ||
$select: ['id'], | ||
$from: 'products' | ||
}; | ||
chai_1.expect(json_sql).to.deep.equal(goal); | ||
}); | ||
mocha_1.test.skip("handles 'any' clause", () => { | ||
const query = { | ||
$where: { | ||
$any: [] | ||
}, | ||
id: true | ||
}; | ||
const json_sql = query_1.query_to_json_sql(query, ['products'], [], {}); | ||
const goal = {}; | ||
chai_1.expect(json_sql).to.deep.equal(goal); | ||
}); | ||
}); | ||
mocha_1.describe(query_1.orma_nester.name, () => { | ||
@@ -610,3 +168,3 @@ mocha_1.test('nests restults', () => { | ||
return Promise.resolve([]); | ||
}); | ||
}, el => el); | ||
chai_1.expect(actual_query).to.deep.equal('SELECT id FROM calls'); | ||
@@ -613,0 +171,0 @@ }); |
@@ -7,2 +7,3 @@ "use strict"; | ||
const query_1 = require("./query"); | ||
const query_helpers_1 = require("./query_helpers"); | ||
const validator = (query, schema) => { | ||
@@ -13,4 +14,4 @@ let errors = []; | ||
const is_boolean_resolver = val === true; | ||
const is_virtual_column_resolver = typeof val === 'object' && !query_1.is_subquery(val); | ||
const is_subquery_resolver = typeof val === 'object' && query_1.is_subquery(val); | ||
const is_virtual_column_resolver = helpers_1.is_simple_object(val) && !query_helpers_1.is_subquery(val); | ||
const is_subquery_resolver = helpers_1.is_simple_object(val) && query_helpers_1.is_subquery(val); | ||
const is_clauses_resolver = '?'; | ||
@@ -17,0 +18,0 @@ if (is_boolean_resolver) { |
{ | ||
"name": "orma", | ||
"version": "1.0.41", | ||
"version": "1.0.43", | ||
"description": "A declarative relational syncronous orm", | ||
@@ -11,2 +11,3 @@ "main": "build/index.js", | ||
"nt": "nodemon --exec nyc --reporter html npm run test", | ||
"ntt": "nodemon --exec npm run test", | ||
"coverage-watch": "live-server coverage" | ||
@@ -13,0 +14,0 @@ }, |
## Orma | ||
Orma is a light-weight declarative ORM for sql databases. | ||
Orma uses a json syntax to represent queries and mutations. | ||
Orma uses json syntax to represent queries and mutations. | ||
Queries are objects specifying which fields to query. Only fields which | ||
@@ -6,0 +6,0 @@ are requested will be selected. Symbols with a $ are called macros and are used to represent abstractions to the sql AST. Sql keywords can be accessed with the $ prefix and snake case. (eg $group_by, $limit, $where) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
94
223545
5564