node-lanetix-list-query-mapper
An adapter that takes an OData URI for querying our stores and maps the payload to a contract.
Operations
Requirements
npm auth token
npm login
(From any directory)- Use your own npmjs login
cd ~/
open .npmrc in your favorite text editor- copy the token that appears after
//registry.npmjs.org/:_authToken= export NPM_TOKEN=<paste token here>
- You can also write this line to your .bashrc, .bash_profile, .zshrc, etc...
- npm install
Installation
npm install -S @lanetix/list-query-mapper
Run Tests
npm install
npm run test
Usage
Odata --> pg results
import createMapper from '@lanetix/list-query-mapper'
import getType from 'promiseToReturnTypeInformation'
import get from 'thingThatExecutesPgQuery'
import getOrganizationConfig from 'records/src/lib/get-organization-config'
export default function getListView: (uri, ...options) {
const mapper = createMapper('pg')
const context = {
organization_config: getOrganizationConfig(settings.organization_id),
options.schema_name,
recordTypeBuilder: getType,
options.recordType,
options.typeInformation,
user
}
return mapper.mapFromOdataUri(uri, context)
.then(get)
.then(mapper.mapToOdataPayload)
}
Odata --> pg fragment (using $apply
)
import createMapper from '@lanetix/list-query-mapper'
import getType from 'promiseToReturnTypeInformation'
import get from 'thingThatExecutesPgQuery'
import getOrganizationConfig from 'records/src/lib/get-organization-config'
import should from 'should'
const uri = "$apply=compute(concat( name, 'e') as elephant)"
export default function (options) {
const mapper = createMapper('pg')
const context = {
organization_config: getOrganizationConfig(settings.organization_id),
options.schema_name,
recordTypeBuilder: getType,
options.recordType,
options.typeInformation,
user
}
const { values, apply: { fragments }} = mapper.mapFromOdataUri(uri, context)
should(values).deepEqual(['e'])
const expected = [{
fragment: '(CONCAT(name, $1::text))',
alias: 'elephant',
type: 'string'
}]
should(fragments).deepEqual(expected)
}
3 Interfaces: uri -> SQL, ast -> SQL, SQL -> pgResults
mapFromOdataUri
const sqlFromUri = mapper.mapFromOdataUri(uri, context)
const { query, values } = sqlFromUri
mapFromOdataAst
import parser from '@lanetix/odata-parser'
import astTransformer from '@lanetix/odata-ast-transformations'
let ast
try {
ast = parser.parse(uri)
} catch (e) {
throw new Error(e)
}
const transformedAst = astTransformer.intersectFilters(
ast, ["$expand=favorite($select=id;$filter=id eq 123)","$filter=name ne 'harry'"]
)
const sqlFromAst = mapper.mapFromOdataAst(transformedAst, context)
const { query, values } = sqlFromAst
mapToOdataPayload
import get from 'thingThatExecutesPgQuery'
const { rows } = mapper.mapFromOdataUri(uri, context)
.then(get)
.then(mapper.mapToOdataPayload)
REPL
npm run repl
Commands in this module are of the format: methodName(ast, context)
.
The repl may be used by either:
- Set the ast, set the context, then call the
withAst.methodName()
. - Set the uri, set the context, then call the
withUri.methodName()
.
In order to set the ast, you may use 2 approaches:
setAstJSON({"tag":"yes"})
// must be a minified JSON formatsetAstFILE('./test-ast.json')
In order to set the uri, you may use 2 approaches:
setUriTEXT("$select=id,lanetix/archived,lanetix/id&$filter=name eq 'Antwan'")
setUriFILE('./test-uri.json')
In order to set the context, you may use 2 approaches:
setContextFILE('./test/data/foo-context.json')
// common sense waysetContextJSON({...crazy huge context file...})
// lunatic way
Result: mapper output is a query
(SQL) and the values
.
Here is an example, with I/O shown:
Denises-MacBook-Pro:node-lanetix-list-query-mapper$ npm run repl
> @lanetix/list-query-mapper@13.3.0 repl /Users/denise/env/dev/node-lanetix-list-query-mapper
> babel-node repl.js
Welcome to the Lanetix odata-ast-transformations REPL.
Please refer to the README.md for the appropriate usage.
> setContextFILE('./test/data/foo-context.json')
Context is set.
> setUriTEXT("$select=id,trash,bass,fess,nullable_string,name,lanetix/archived,lanetix/id&$filter=name eq 'Antwan'")
Set URI:
$select=id,trash,bass,fess,nullable_string,name,lanetix/archived,lanetix/id&$filter=name eq 'Antwan'
> withUri.mapFromOdataUri()
> SQL:
SELECT id, trash, bass, fess, nullable_string, name, json_build_object('archived', (lanetix).archived, 'id', (lanetix).id)::jsonb as lanetix
FROM records_2731111."view.foo" WHERE (lower(name) COLLATE "C") = ($1::text)
VALUES:
["antwan"]
> q
Denises-MacBook-Pro:node-lanetix-list-query-mapper$
Concept
- Takes a URI that follows OData 4.0 URI Specification and returns data from PG.
- Converts the Abstract Syntax Tree (AST) to the final form will require using visitors and mapping.
File Structure
src/mappers
entry point into the mappers.src/lib/
the mapper files for each SQL dialect.
OData Structure
- lx has not implemented all of the odata spec. Here is a summary of what is implemented.
GET recordType?$queryOption&$queryOption
- query options:
$search
= search feature. odata node type 'functioncall' with func 'substringof'. creates pg sql for LIKE.$select
= a list of fieldNames
$select=name,close_date,chance_to_win,contract_length_years
$apply
= for set transformations. Also has the compute transformation (for any commonExpn).
recordType?$apply=concat(set_1, set_2)
recordType?$apply=compute(commonExpn as aliasName)
recordType?$apply=compute(concat(name, "is a frog") as aliasName)
- this query option can not be used in combination with any other query option.
$filter
= boolean expressions, connected by and|or.
- currently we support:
- math operators add|sub|mul
- functions listed in
src/lib/pg/operators/functioncall
- comparison operators eq|ne|lt|gt|le|ge
- conditional operators and|or
- limitations:
$expand
= related fields. Will then include it's $select.
&$expand=project_account_id($select=lanetix/id;$expand=owner($select=lanetix/id,first_name))
$orderby
= orderby. Takes list of properties and asc|desc.
&$orderby=name asc
$count
$skip
= offset. Takes integer. Used in pagination.$top
= limit. Takes integer. Used in pagination.
&$top=20
- use of parans and whitespace:
Parallel Paths through the mapper
Differences from OData specs
- We use the parameter-alias as a Lanetix lx-parameter-alias. Uses similar syntax for the identifier (
@lx_myTeam
), but the user cannot provide an expression which the identifier is equal to (unlike the OData spec).
$filter=nullable_integer eq @lx_myUser_Id and nullable_integer eq @lx_myTeam and
nullable_integer eq @lx_myOrg_Id and nullable_string eq @lx_myUser_Timezone
- We use the route (api access) url a bit different from the spec.
// per odata spec
// http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html
http://localhost/Products?$orderby=Name
// Lx
http://localhost/v1/insights/o?recordType=products&odata=$orderby=name asc
- The odata spec has an implied
SELECT *
if no $select
query option is provided.
vs.
Lanetix requires a $select
query option. Because returning all relation attributes is too large.
In aggregations, we also require an explicit select, by use of a final compute transformation. $apply=transformation1()/transformation2()/compute(path as alias1, path as alias2)
- When referring to related fields (related/related/field), for both the
$select
&$filter
&expand
and the $apply
, Lanetix requires that an expand be explicitly stated. The OASIS spec is unclear (for all circumstances) as to whether or not this is a typically requirement, but Lx definitely requires it. - The
$apply
SPEC also includes other deviations. See here.
Arrows vs Fibers (types of JOINs)
Currently supported dialects.