diff --git a/src/xsprintf/qsprintf.php b/src/xsprintf/qsprintf.php index f1287f2..5627841 100644 --- a/src/xsprintf/qsprintf.php +++ b/src/xsprintf/qsprintf.php @@ -1,308 +1,313 @@ and %<. * * %> ("Prefix") * Escapes a prefix query for a LIKE clause. For example: * * // Find all rows where `name` starts with $prefix. * qsprintf($escaper, 'WHERE name LIKE %>', $prefix); * * %< ("Suffix") * Escapes a suffix query for a LIKE clause. For example: * * // Find all rows where `name` ends with $suffix. * qsprintf($escaper, 'WHERE name LIKE %<', $suffix); */ function qsprintf(PhutilQsprintfInterface $escaper, $pattern /* , ... */) { $args = func_get_args(); array_shift($args); return xsprintf('xsprintf_query', $escaper, $args); } function vqsprintf(PhutilQsprintfInterface $escaper, $pattern, array $argv) { array_unshift($argv, $pattern); return xsprintf('xsprintf_query', $escaper, $argv); } /** * @{function:xsprintf} callback for encoding SQL queries. See * @{function:qsprintf}. */ function xsprintf_query($userdata, &$pattern, &$pos, &$value, &$length) { $type = $pattern[$pos]; $escaper = $userdata; $next = (strlen($pattern) > $pos + 1) ? $pattern[$pos + 1] : null; $nullable = false; $done = false; $prefix = ''; if (!($escaper instanceof PhutilQsprintfInterface)) { throw new InvalidArgumentException('Invalid database escaper.'); } switch ($type) { case '=': // Nullable test switch ($next) { case 'd': case 'f': case 's': $pattern = substr_replace($pattern, '', $pos, 1); $length = strlen($pattern); $type = 's'; if ($value === null) { $value = 'IS NULL'; $done = true; } else { $prefix = '= '; $type = $next; } break; default: throw new Exception('Unknown conversion, try %=d, %=s, or %=f.'); } break; case 'n': // Nullable... switch ($next) { case 'd': // ...integer. case 'f': // ...float. case 's': // ...string. case 'B': // ...binary string. $pattern = substr_replace($pattern, '', $pos, 1); $length = strlen($pattern); $type = $next; $nullable = true; break; default: throw new XsprintfUnknownConversionException("%n{$next}"); } break; case 'L': // List of.. _qsprintf_check_type($value, "L{$next}", $pattern); $pattern = substr_replace($pattern, '', $pos, 1); $length = strlen($pattern); $type = 's'; $done = true; switch ($next) { case 'd': // ...integers. $value = implode(', ', array_map('intval', $value)); break; + case 'f': // ...floats. + $value = implode(', ', array_map('floatval', $value)); + break; case 's': // ...strings. foreach ($value as $k => $v) { $value[$k] = "'".$escaper->escapeUTF8String((string)$v)."'"; } $value = implode(', ', $value); break; case 'B': // ...binary strings. foreach ($value as $k => $v) { $value[$k] = "'".$escaper->escapeBinaryString((string)$v)."'"; } $value = implode(', ', $value); break; case 'C': // ...columns. foreach ($value as $k => $v) { $value[$k] = $escaper->escapeColumnName($v); } $value = implode(', ', $value); break; default: throw new XsprintfUnknownConversionException("%L{$next}"); } break; } if (!$done) { _qsprintf_check_type($value, $type, $pattern); switch ($type) { case 's': // String if ($nullable && $value === null) { $value = 'NULL'; } else { $value = "'".$escaper->escapeUTF8String((string)$value)."'"; } $type = 's'; break; case 'B': // Binary String if ($nullable && $value === null) { $value = 'NULL'; } else { $value = "'".$escaper->escapeBinaryString((string)$value)."'"; } $type = 's'; break; case 'Q': // Query Fragment $type = 's'; break; case '~': // Like Substring case '>': // Like Prefix case '<': // Like Suffix $value = $escaper->escapeStringForLikeClause($value); switch ($type) { case '~': $value = "'%".$value."%'"; break; case '>': $value = "'".$value."%'"; break; case '<': $value = "'%".$value."'"; break; } $type = 's'; break; case 'f': // Float if ($nullable && $value === null) { $value = 'NULL'; } else { $value = (float)$value; } $type = 's'; break; case 'd': // Integer if ($nullable && $value === null) { $value = 'NULL'; } else { $value = (int)$value; } $type = 's'; break; case 'T': // Table case 'C': // Column $value = $escaper->escapeColumnName($value); $type = 's'; break; case 'K': // Komment $value = $escaper->escapeMultilineComment($value); $type = 's'; break; default: throw new XsprintfUnknownConversionException($type); } } if ($prefix) { $value = $prefix.$value; } $pattern[$pos] = $type; } function _qsprintf_check_type($value, $type, $query) { switch ($type) { case 'Ld': case 'Ls': case 'LC': case 'LB': + case 'Lf': if (!is_array($value)) { throw new AphrontParameterQueryException( $query, "Expected array argument for %{$type} conversion."); } if (empty($value)) { throw new AphrontParameterQueryException( $query, "Array for %{$type} conversion is empty."); } foreach ($value as $scalar) { _qsprintf_check_scalar_type($scalar, $type, $query); } break; default: _qsprintf_check_scalar_type($value, $type, $query); break; } } function _qsprintf_check_scalar_type($value, $type, $query) { switch ($type) { case 'Q': case 'LC': case 'T': case 'C': if (!is_string($value)) { throw new AphrontParameterQueryException( $query, "Expected a string for %{$type} conversion."); } break; case 'Ld': + case 'Lf': case 'd': case 'f': if (!is_null($value) && !is_numeric($value)) { throw new AphrontParameterQueryException( $query, "Expected a numeric scalar or null for %{$type} conversion."); } break; case 'Ls': case 's': case 'LB': case 'B': case '~': case '>': case '<': case 'K': if (!is_null($value) && !is_scalar($value)) { throw new AphrontParameterQueryException( $query, "Expected a scalar or null for %{$type} conversion."); } break; default: throw new XsprintfUnknownConversionException($type); } }