mekanika / qe

Query envelope (Qe) specification
2 stars 0 forks source link

Nested matches #2

Closed cayuu closed 9 years ago

cayuu commented 10 years ago

Handle:

(name = 'x' && age >= 21) || ( state = 'CA' || (state = 'WA' && age >= 18) )
cayuu commented 9 years ago

The current 0.4.0 match spec and match object spec only allows for a list of conditions, with no relation between them. Currently:

{match: [
  {field:'age', op:'gte', value:'18'},
  {field:'criminal', op:'eq', value:true}
]}

The spec does not explain whether the list is to be AND or OR. Either convention excludes the other (and every alternative boolean operator). Further, no nesting is currently possible.

cayuu commented 9 years ago

MongoDB specifies nesting as follows:

{
    $and : [
        { $or : [ { price : 0.99 }, { price : 1.99 } ] },
        { $or : [ { sale : true }, { qty : { $lt : 20 } } ] }
    ]
}

Translates to: ( price = 0.99 || price = 1.99 ) && ( sale = true || qty < 20 )

The nested challenge opening this question would represent in Mongo-style as:

// (name = 'x' && age >= 21) || ( state = 'CA' || (state = 'WA' && age >= 18) )
{
  $or: [
    { $and: [{name:'x'}, {age: {$gte:21}} ] },
    { $or: [ 
      {state:'CA'}, 
      { $or: [ {state:'WA'}, {age: {$gte:18}} ] } 
    ]}
  ]
}

This is ugly to parse.

cayuu commented 9 years ago

One alternative is a naive [mo, op, mo], op, mo style approach, where arrays are blocks:

[
  [ {name:x}, 'and', {age: {$gte:21}}],
  'or',
  [
    {state:'CA'},
    'or',
    [ {state:'WA'}, 'and', {age:{$gte:18}} ]
  ]
]

Perhaps all nesting is simply ugly.

cayuu commented 9 years ago

Trying to consider generating nested matches using fluent interfaces:

.anded( [{name:'x'}, {age:$gte:21}] )
.some_link_to_say_OR_joins_these_two_blocks().
.ored( [ {state:'CA', HOW_TO_FLIENT_EMBED_ANOTHER_AND_BLOCK??!!?! ] )

But to actually NEST you basically end up with a code version of the mongo block (assuming 'and()' and 'or()' are accessible functions that generate Qo compatible match objects):

or(
  and([ {name:'x'}, {age: {$gte:21}} ]),
  or([
    {state:'CA'},
    or([ {state:'WA'}, {age: {$gte:18}} ])
  ]) 
}

This all sucks. Nesting sucks.

cayuu commented 9 years ago

"Deep matching" on nested conditions may be a real pita.

cayuu commented 9 years ago

Worth noting that current 0.4.0 match object prevents concise descriptions of some types of matches on a single field. Take the following:

Age between 18 and 21, but not 20

Breaks down to:

gte:18 AND lte:21 AND neq:20

// Concise:
{age: {gte:18, lte:21, neq:20} } // Neat! However implies/assumes an "AND" association

// 0.4.0 spec:
{field:'age', op:'gte', value:18}, // AND operator somewhere
{field:'age', op:'lte', value:21}, // AND operator somewhere
{field:'age', op:'neq', value:20}

Conciseness breakdown somewhat If our conditions deviate from the convention boolean operator. eg:

Age more than 18 or less than 12

gt:18 OR lt:12

// Concise:
{age: {gt:18}},
{age: {lt: 12}} // Requires splitting the match operators to allow an alternative bool operator

// 0.4.0 spec
{field:'age', op:'gt', value:18}, // OR operator somewhere
{field:'age', op:'lt', value:12},

This looks less terrible when broadening use cases outside of a single assumed operator (AND). It's still fairly verbose, but it's trivial to parse by code and by human. +1 for holding up.

cayuu commented 9 years ago

Lets assume: mo is a match object {field:'f', op:'op', value:v}.

Then, let a match container (mc) be defined as:

{ '$boolOp': [ mo|mc...  ] }

Where:

In this way match container mc may nest. An mc MUST contain only one $boolOp.

Qo match field can now simply be a match container mc.

match: {
  'and': [ {field:'age', op:'gte', value:18}, {field:'age', op:'lte', value:21} ]
}

This is exactly how MongoDB does it. This is the sound of one hand clapping. Good job.

cayuu commented 9 years ago

Landed as of commit a87e7e542bf230