import { AnyObject , parseGeoPoint , startsWith , isGeoJSON , isGeoShapePolygon , isGeoShapeMultiPolygon } from '@terascope/utils' ; import { GeoPointInput , xLuceneTypeConfig , xLuceneVariables , xLuceneFieldType , GeoShapeRelation } from '@terascope/types' ; import { isGeoShapePoint } from '../validations/field-validator' ; export type JoinBy = 'AND' | 'OR' ; export interface xLuceneQueryResult { query : string ; variables : xLuceneVariables ; } export type CreateJoinQueryOptions = { typeConfig ? : xLuceneTypeConfig ; fieldParams ? : Record < string , string > ; joinBy ? : JoinBy ; variables ? : AnyObject ; } ; function isGeoQuery ( type ) { return isGeoPointType ( type ) || isGeoJSONType ( type ) ; } function isGeoPointType ( type ) { return type === xLuceneFieldType . Geo || type === xLuceneFieldType . GeoPoint ; } function isGeoJSONType ( type ) { return type === xLuceneFieldType . GeoJSON ; } const relationList = Object . values ( GeoShapeRelation ) ; function makeXluceneGeoDistanceQuery ( variableState , field , value , fieldParam ? ) { const dValue = fieldParam || '100m' ; const results = parseGeoPoint ( value , false ) ; if ( ! results ) return '' ; const vDistance = variableState . createVariable ( 'distance' , dValue ) ; const vPoint = variableState . createVariable ( 'point' , `${ results . lat },${ results . lon }` ) ; return `${ field }:geoDistance(point: ${ vPoint }, distance: ${ vDistance })` ; } function makeXlucenePolyContainsPoint ( variableState , field , value ) { const results = parseGeoPoint ( value , false ) ; if ( ! results ) return '' ; const vPoint = variableState . createVariable ( 'point' , `${ results . lat },${ results . lon }` ) ; return `${ field }:geoContainsPoint(point: ${ vPoint })` ; } function geoPolyQuery ( field , polyVariableName , vParam ? ) { if ( vParam ) { return `${ field }:geoPolygon(points: ${ polyVariableName }, relation: ${ vParam })` ; } return `${ field }:geoPolygon(points: ${ polyVariableName })` ; } function makeXlucenePolyQuery ( variableState , field , polyVariableName , fieldParam ? ) { let vParam ; if ( fieldParam && relationList . includes ( fieldParam as GeoShapeRelation ) ) { vParam = variableState . createVariable ( 'relation' , fieldParam ) as string ; } return geoPolyQuery ( field , polyVariableName , vParam ) ; } function createGeoQuery ( variableState , field , value , targetType , fieldParam ? ) { if ( isGeoJSON ( value ) ) { if ( isGeoShapePolygon ( value ) || isGeoShapeMultiPolygon ( value ) ) { const polyVariableName = variableState . createVariable ( 'points' , value ) ; return makeXlucenePolyQuery ( variableState , field , polyVariableName , fieldParam ) ; } if ( isGeoShapePoint ( value ) ) { if ( isGeoPointType ( targetType ) ) { return makeXluceneGeoDistanceQuery ( variableState , field , value . coordinates , fieldParam ) ; } return makeXlucenePolyContainsPoint ( variableState , field , value . coordinates ) ; } return '' ; } if ( isGeoPointType ( targetType ) ) { return makeXluceneGeoDistanceQuery ( variableState , field , value , fieldParam ) ; } if ( isGeoJSONType ( targetType ) ) return makeXlucenePolyContainsPoint ( variableState , field , value ) ; return '' ; } export function toXluceneQuery ( input , options = { } ) { const { fieldParams = { } , joinBy = 'AND' , typeConfig = { } , variables } = options ; const variableState = new VariableState ( variables ) ; const query = Object . entries ( input ) . map ( ( [ field , val ] ) => { if ( val == null ) return '' ; const config = typeConfig [ field ] ; if ( isGeoQuery ( config ) ) { return createGeoQuery ( variableState , field , val , config , fieldParams [ field ] ) ; } const value = variableState . createVariable ( field , val ) ; return `${ field }: ${ value }` ; } ) . filter ( Boolean ) . join ( ` ${ joinBy } ` ) . trim ( ) ; const finalVariables = variableState . getVariables ( ) ; return { query , variables : finalVariables } ; } export class VariableState { private variables : AnyObject ; constructor ( variables ? ) { this . variables = { ... variables } ; } private _makeKey ( field ) { let num = 1 ; let name = `${ field }_${ num }` ; while ( this . variables [ name ] !== undefined ) { num += 1 ; name = `${ field }_${ num }` ; } return name ; } createVariable ( field , value ) { if ( typeof value === 'string' && startsWith ( value , '$' ) ) { const vField = value . slice ( 1 ) ; if ( this . variables [ vField ] === undefined ) { throw new Error ( `Must provide variable "${ vField }" in the variables config` ) ; } return value ; } const key = this . _makeKey ( field ) ; this . variables [ key ] = value ; return `$${ key }` ; } /**
     * Shallow clones and sorts the keys
    */ getVariables ( ) { const result = { } ; for ( const key of Object . keys ( this . variables ) . sort ( ) ) { result [ key ] = this . variables [ key ] ; } return result ; } }