Implement In/InArray

- WIP on testdox name changes
This commit is contained in:
2024-09-22 17:44:07 -04:00
parent e830b1ac3e
commit 294b608ac8
9 changed files with 402 additions and 116 deletions

View File

@@ -46,12 +46,48 @@ class Field
$existing["{$this->paramName}min"] = $this->value[0];
$existing["{$this->paramName}max"] = $this->value[1];
break;
case Op::In:
for ($idx = 0; $idx < count($this->value); $idx++) {
$existing["{$this->paramName}_$idx"] = $this->value[$idx];
}
break;
case Op::InArray:
$mkString = Configuration::mode("Append parameters for InArray condition") === Mode::PgSQL;
$values = $this->value['values'];
for ($idx = 0; $idx < count($values); $idx++) {
$existing["{$this->paramName}_$idx"] = $mkString ? "$values[$idx]" : $values[$idx];
}
break;
default:
$existing[$this->paramName] = $this->value;
}
return $existing;
}
/**
* Derive the path for this field
*
* @param bool $asJSON Whether the field should be treated as JSON in the query (optional, default false)
* @return string The path for this field
* @throws Exception If the database mode has not been set
*/
public function path(bool $asJSON = false): string
{
$extra = $asJSON ? '' : '>';
if (str_contains($this->fieldName, '.')) {
$mode = Configuration::mode('determine field path');
if ($mode === Mode::PgSQL) {
return "data#>$extra'{" . implode(',', explode('.', $this->fieldName)) . "}'";
}
if ($mode === Mode::SQLite) {
$parts = explode('.', $this->fieldName);
$last = array_pop($parts);
return "data->'" . implode("'->'", $parts) . "'->$extra'$last'";
}
}
return "data->$extra'$this->fieldName'";
}
/**
* Get the WHERE clause fragment for this parameter
*
@@ -60,27 +96,41 @@ class Field
*/
public function toWhere(): string
{
$mode = Configuration::mode('make field WHERE clause');
$fieldName = (empty($this->qualifier) ? '' : "$this->qualifier.") . 'data' . match (true) {
!str_contains($this->fieldName, '.') => "->>'$this->fieldName'",
$mode === Mode::PgSQL => "#>>'{" . implode(',', explode('.', $this->fieldName)) . "}'",
$mode === Mode::SQLite => "->>'" . implode("'->>'", explode('.', $this->fieldName)) . "'",
};
$fieldPath = match ($mode) {
$mode = Configuration::mode('make field WHERE clause');
$fieldName = (empty($this->qualifier) ? '' : "$this->qualifier.") . $this->path($this->op === Op::InArray);
$fieldPath = match ($mode) {
Mode::PgSQL => match (true) {
$this->op === Op::Between => is_numeric($this->value[0]) ? "($fieldName)::numeric" : $fieldName,
$this->op === Op::Between,
$this->op === Op::In => is_numeric($this->value[0]) ? "($fieldName)::numeric" : $fieldName,
is_numeric($this->value) => "($fieldName)::numeric",
default => $fieldName,
},
default => $fieldName,
};
$criteria = match ($this->op) {
Op::Exists, Op::NotExists => '',
Op::Between => " {$this->paramName}min AND {$this->paramName}max",
Op::In => "TODO",
default => " $this->paramName",
Op::Exists,
Op::NotExists => '',
Op::Between => " {$this->paramName}min AND {$this->paramName}max",
Op::In => ' (' . $this->inParameterNames() . ')',
Op::InArray => $mode === Mode::PgSQL ? ' ARRAY[' . $this->inParameterNames() . ']' : '',
default => " $this->paramName",
};
return $fieldPath . ' ' . $this->op->toSQL() . $criteria;
return $mode === Mode::SQLite && $this->op === Op::InArray
? "EXISTS (SELECT 1 FROM json_each({$this->value['table']}.data, '\$.$this->fieldName') WHERE value IN ("
. $this->inParameterNames() . '))'
: $fieldPath . ' ' . $this->op->toSQL() . $criteria;
}
/**
* Create parameter names for an IN clause
*
* @return string A comma-delimited string of parameter names
*/
private function inParameterNames(): string
{
$values = $this->op === Op::In ? $this->value : $this->value['values'];
return implode(', ',
array_map(fn($value, $key) => "{$this->paramName}_$key", $values, range(0, count($values) - 1)));
}
/**
@@ -284,13 +334,14 @@ class Field
* Create an IN ARRAY field criterion
*
* @param string $fieldName The name of the field against which the values will be compared
* @param string $tableName The table name where this field is located
* @param mixed[] $values The potential matching values for the field
* @param string $paramName The name of the parameter to which this should be bound (optional; generated if blank)
* @return self The field with the requested criterion
*/
public static function inArray(string $fieldName, array $values, string $paramName = ''): self
public static function inArray(string $fieldName, string $tableName, array $values, string $paramName = ''): self
{
return new self($fieldName, Op::InArray, $values, $paramName);
return new self($fieldName, Op::InArray, ['table' => $tableName, 'values' => $values], $paramName);
}
/**