mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-20 18:04:19 +01:00
Merge branch 'main' into json-error-position
This commit is contained in:
15
.github/shared/install_sqlite/action.yml
vendored
Normal file
15
.github/shared/install_sqlite/action.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: "Install SQLite"
|
||||||
|
description: "Downloads SQLite directly from https://sqlite.org"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Install SQLite
|
||||||
|
env:
|
||||||
|
SQLITE_VERSION: "3470200"
|
||||||
|
YEAR: 2024
|
||||||
|
run: |
|
||||||
|
curl -o /tmp/sqlite.zip https://www.sqlite.org/$YEAR/sqlite-tools-linux-x64-$SQLITE_VERSION.zip > /dev/null
|
||||||
|
unzip -j /tmp/sqlite.zip sqlite3 -d /usr/local/bin/
|
||||||
|
sqlite3 --version
|
||||||
|
shell: bash
|
||||||
36
.github/workflows/rust.yml
vendored
36
.github/workflows/rust.yml
vendored
@@ -59,31 +59,29 @@ jobs:
|
|||||||
bench:
|
bench:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Bench
|
- name: Bench
|
||||||
run: cargo bench
|
run: cargo bench
|
||||||
|
|
||||||
test-limbo:
|
test-limbo:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install sqlite
|
- name: Install cargo-c
|
||||||
run: sudo apt update && sudo apt install -y sqlite3 libsqlite3-dev
|
env:
|
||||||
- name: Install cargo-c
|
LINK: https://github.com/lu-zero/cargo-c/releases/download/v0.10.7
|
||||||
env:
|
CARGO_C_FILE: cargo-c-x86_64-unknown-linux-musl.tar.gz
|
||||||
LINK: https://github.com/lu-zero/cargo-c/releases/download/v0.10.7
|
run: |
|
||||||
CARGO_C_FILE: cargo-c-x86_64-unknown-linux-musl.tar.gz
|
curl -L $LINK/$CARGO_C_FILE | tar xz -C ~/.cargo/bin
|
||||||
run: |
|
|
||||||
curl -L $LINK/$CARGO_C_FILE | tar xz -C ~/.cargo/bin
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Test
|
- uses: "./.github/shared/install_sqlite"
|
||||||
run: make test
|
- name: Test
|
||||||
|
run: make test
|
||||||
|
|
||||||
test-sqlite:
|
test-sqlite:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Install sqlite
|
- uses: "./.github/shared/install_sqlite"
|
||||||
run: sudo apt update && sudo apt install -y sqlite3 libsqlite3-dev
|
- name: Test
|
||||||
- name: Test
|
run: SQLITE_EXEC="sqlite3" make test-compat
|
||||||
run: SQLITE_EXEC="sqlite3" make test-compat
|
|
||||||
|
|||||||
@@ -266,8 +266,8 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
|
|||||||
| json_error_position(json) | Yes | |
|
| json_error_position(json) | Yes | |
|
||||||
| json_extract(json,path,...) | Partial | Does not fully support unicode literal syntax and does not allow numbers > 2^127 - 1 (which SQLite truncates to i32), does not support BLOBs |
|
| json_extract(json,path,...) | Partial | Does not fully support unicode literal syntax and does not allow numbers > 2^127 - 1 (which SQLite truncates to i32), does not support BLOBs |
|
||||||
| jsonb_extract(json,path,...) | | |
|
| jsonb_extract(json,path,...) | | |
|
||||||
| json -> path | | |
|
| json -> path | Yes | |
|
||||||
| json ->> path | | |
|
| json ->> path | Yes | |
|
||||||
| json_insert(json,path,value,...) | | |
|
| json_insert(json,path,value,...) | | |
|
||||||
| jsonb_insert(json,path,value,...) | | |
|
| jsonb_insert(json,path,value,...) | | |
|
||||||
| json_object(label1,value1,...) | | |
|
| json_object(label1,value1,...) | | |
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ version = "0.0.11"
|
|||||||
authors = ["the Limbo authors"]
|
authors = ["the Limbo authors"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/penberg/limbo"
|
repository = "https://github.com/tursodatabase/limbo"
|
||||||
|
|
||||||
# Config for 'cargo dist'
|
# Config for 'cargo dist'
|
||||||
[workspace.metadata.dist]
|
[workspace.metadata.dist]
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ impl<'a> ImportFile<'a> {
|
|||||||
|
|
||||||
for r in record.iter() {
|
for r in record.iter() {
|
||||||
values_string.push('\'');
|
values_string.push('\'');
|
||||||
values_string.push_str(r);
|
// The string can have a single quote which needs to be escaped
|
||||||
|
values_string.push_str(&r.replace("'", "''"));
|
||||||
values_string.push_str("',");
|
values_string.push_str("',");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ impl Display for ExternalFunc {
|
|||||||
pub enum JsonFunc {
|
pub enum JsonFunc {
|
||||||
Json,
|
Json,
|
||||||
JsonArray,
|
JsonArray,
|
||||||
JsonExtract,
|
|
||||||
JsonArrayLength,
|
JsonArrayLength,
|
||||||
|
JsonArrowExtract,
|
||||||
|
JsonArrowShiftExtract,
|
||||||
|
JsonExtract,
|
||||||
JsonType,
|
JsonType,
|
||||||
JsonErrorPosition,
|
JsonErrorPosition,
|
||||||
}
|
}
|
||||||
@@ -42,6 +44,8 @@ impl Display for JsonFunc {
|
|||||||
Self::JsonArray => "json_array".to_string(),
|
Self::JsonArray => "json_array".to_string(),
|
||||||
Self::JsonExtract => "json_extract".to_string(),
|
Self::JsonExtract => "json_extract".to_string(),
|
||||||
Self::JsonArrayLength => "json_array_length".to_string(),
|
Self::JsonArrayLength => "json_array_length".to_string(),
|
||||||
|
Self::JsonArrowExtract => "->".to_string(),
|
||||||
|
Self::JsonArrowShiftExtract => "->>".to_string(),
|
||||||
Self::JsonType => "json_type".to_string(),
|
Self::JsonType => "json_type".to_string(),
|
||||||
Self::JsonErrorPosition => "json_error_position".to_string(),
|
Self::JsonErrorPosition => "json_error_position".to_string(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
negative_index_indicator = ${ "#-" }
|
negative_index_indicator = ${ "#-" }
|
||||||
array_offset = ${ ASCII_DIGIT+ }
|
array_offset = ${ ASCII_DIGIT+ }
|
||||||
array_locator = ${ "[" ~ negative_index_indicator? ~ array_offset ~ "]" }
|
array_locator = ${ "[" ~ negative_index_indicator? ~ array_offset ~ "]" }
|
||||||
|
relaxed_array_locator = ${ negative_index_indicator? ~ array_offset }
|
||||||
|
|
||||||
root = ${ "$" }
|
root = ${ "$" }
|
||||||
json_path_key = ${ identifier | string }
|
json_path_key = ${ identifier | string }
|
||||||
|
|||||||
142
core/json/mod.rs
142
core/json/mod.rs
@@ -7,7 +7,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
pub use crate::json::de::from_str;
|
pub use crate::json::de::from_str;
|
||||||
use crate::json::error::Error as JsonError;
|
use crate::json::error::Error as JsonError;
|
||||||
use crate::json::json_path::{json_path, PathElement};
|
use crate::json::json_path::{json_path, JsonPath, PathElement};
|
||||||
pub use crate::json::ser::to_string;
|
pub use crate::json::ser::to_string;
|
||||||
use crate::types::{LimboText, OwnedValue, TextSubtype};
|
use crate::types::{LimboText, OwnedValue, TextSubtype};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
@@ -126,7 +126,7 @@ pub fn json_array_length(
|
|||||||
let json = get_json_value(json_value)?;
|
let json = get_json_value(json_value)?;
|
||||||
|
|
||||||
let arr_val = if let Some(path) = json_path {
|
let arr_val = if let Some(path) = json_path {
|
||||||
match json_extract_single(&json, path)? {
|
match json_extract_single(&json, path, true)? {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
None => return Ok(OwnedValue::Null),
|
None => return Ok(OwnedValue::Null),
|
||||||
}
|
}
|
||||||
@@ -141,6 +141,44 @@ pub fn json_array_length(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements the -> operator. Always returns a proper JSON value.
|
||||||
|
/// https://sqlite.org/json1.html#the_and_operators
|
||||||
|
pub fn json_arrow_extract(value: &OwnedValue, path: &OwnedValue) -> crate::Result<OwnedValue> {
|
||||||
|
if let OwnedValue::Null = value {
|
||||||
|
return Ok(OwnedValue::Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = get_json_value(value)?;
|
||||||
|
let extracted = json_extract_single(&json, path, false)?;
|
||||||
|
|
||||||
|
if let Some(val) = extracted {
|
||||||
|
let json = crate::json::to_string(val).unwrap();
|
||||||
|
|
||||||
|
Ok(OwnedValue::Text(LimboText::json(Rc::new(json))))
|
||||||
|
} else {
|
||||||
|
Ok(OwnedValue::Null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements the ->> operator. Always returns a SQL representation of the JSON subcomponent.
|
||||||
|
/// https://sqlite.org/json1.html#the_and_operators
|
||||||
|
pub fn json_arrow_shift_extract(
|
||||||
|
value: &OwnedValue,
|
||||||
|
path: &OwnedValue,
|
||||||
|
) -> crate::Result<OwnedValue> {
|
||||||
|
if let OwnedValue::Null = value {
|
||||||
|
return Ok(OwnedValue::Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = get_json_value(value)?;
|
||||||
|
let extracted = json_extract_single(&json, path, false)?.unwrap_or_else(|| &Val::Null);
|
||||||
|
|
||||||
|
convert_json_to_db_type(extracted, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a JSON value from a JSON object or array.
|
||||||
|
/// If there's only a single path, the return value might be either a TEXT or a database type.
|
||||||
|
/// https://sqlite.org/json1.html#the_json_extract_function
|
||||||
pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<OwnedValue> {
|
||||||
if let OwnedValue::Null = value {
|
if let OwnedValue::Null = value {
|
||||||
return Ok(OwnedValue::Null);
|
return Ok(OwnedValue::Null);
|
||||||
@@ -148,14 +186,15 @@ pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<O
|
|||||||
|
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
return Ok(OwnedValue::Null);
|
return Ok(OwnedValue::Null);
|
||||||
|
} else if paths.len() == 1 {
|
||||||
|
let json = get_json_value(value)?;
|
||||||
|
let extracted = json_extract_single(&json, &paths[0], true)?.unwrap_or_else(|| &Val::Null);
|
||||||
|
|
||||||
|
return convert_json_to_db_type(&extracted, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let json = get_json_value(value)?;
|
let json = get_json_value(value)?;
|
||||||
let mut result = "".to_string();
|
let mut result = "[".to_string();
|
||||||
|
|
||||||
if paths.len() > 1 {
|
|
||||||
result.push('[');
|
|
||||||
}
|
|
||||||
|
|
||||||
for path in paths {
|
for path in paths {
|
||||||
match path {
|
match path {
|
||||||
@@ -163,28 +202,59 @@ pub fn json_extract(value: &OwnedValue, paths: &[OwnedValue]) -> crate::Result<O
|
|||||||
return Ok(OwnedValue::Null);
|
return Ok(OwnedValue::Null);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let extracted = json_extract_single(&json, path)?.unwrap_or_else(|| &Val::Null);
|
let extracted =
|
||||||
|
json_extract_single(&json, path, true)?.unwrap_or_else(|| &Val::Null);
|
||||||
|
|
||||||
if paths.len() == 1 && extracted == &Val::Null {
|
if paths.len() == 1 && extracted == &Val::Null {
|
||||||
return Ok(OwnedValue::Null);
|
return Ok(OwnedValue::Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push_str(&crate::json::to_string(&extracted).unwrap());
|
result.push_str(&crate::json::to_string(&extracted).unwrap());
|
||||||
if paths.len() > 1 {
|
result.push(',');
|
||||||
result.push(',');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if paths.len() > 1 {
|
result.pop(); // remove the final comma
|
||||||
result.pop(); // remove the final comma
|
result.push(']');
|
||||||
result.push(']');
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(OwnedValue::Text(LimboText::json(Rc::new(result))))
|
Ok(OwnedValue::Text(LimboText::json(Rc::new(result))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a value with type defined by SQLite documentation:
|
||||||
|
/// > the SQL datatype of the result is NULL for a JSON null,
|
||||||
|
/// > INTEGER or REAL for a JSON numeric value,
|
||||||
|
/// > an INTEGER zero for a JSON false value,
|
||||||
|
/// > an INTEGER one for a JSON true value,
|
||||||
|
/// > the dequoted text for a JSON string value,
|
||||||
|
/// > and a text representation for JSON object and array values.
|
||||||
|
/// https://sqlite.org/json1.html#the_json_extract_function
|
||||||
|
///
|
||||||
|
/// *all_as_db* - if true, objects and arrays will be returned as pure TEXT without the JSON subtype
|
||||||
|
fn convert_json_to_db_type(extracted: &Val, all_as_db: bool) -> crate::Result<OwnedValue> {
|
||||||
|
match extracted {
|
||||||
|
Val::Null => Ok(OwnedValue::Null),
|
||||||
|
Val::Float(f) => Ok(OwnedValue::Float(*f)),
|
||||||
|
Val::Integer(i) => Ok(OwnedValue::Integer(*i)),
|
||||||
|
Val::Bool(b) => {
|
||||||
|
if *b {
|
||||||
|
Ok(OwnedValue::Integer(1))
|
||||||
|
} else {
|
||||||
|
Ok(OwnedValue::Integer(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Val::String(s) => Ok(OwnedValue::Text(LimboText::new(Rc::new(s.clone())))),
|
||||||
|
_ => {
|
||||||
|
let json = crate::json::to_string(&extracted).unwrap();
|
||||||
|
if all_as_db {
|
||||||
|
Ok(OwnedValue::Text(LimboText::new(Rc::new(json))))
|
||||||
|
} else {
|
||||||
|
Ok(OwnedValue::Text(LimboText::json(Rc::new(json))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn json_type(value: &OwnedValue, path: Option<&OwnedValue>) -> crate::Result<OwnedValue> {
|
pub fn json_type(value: &OwnedValue, path: Option<&OwnedValue>) -> crate::Result<OwnedValue> {
|
||||||
if let OwnedValue::Null = value {
|
if let OwnedValue::Null = value {
|
||||||
return Ok(OwnedValue::Null);
|
return Ok(OwnedValue::Null);
|
||||||
@@ -193,7 +263,7 @@ pub fn json_type(value: &OwnedValue, path: Option<&OwnedValue>) -> crate::Result
|
|||||||
let json = get_json_value(value)?;
|
let json = get_json_value(value)?;
|
||||||
|
|
||||||
let json = if let Some(path) = path {
|
let json = if let Some(path) = path {
|
||||||
match json_extract_single(&json, path)? {
|
match json_extract_single(&json, path, true)? {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
None => return Ok(OwnedValue::Null),
|
None => return Ok(OwnedValue::Null),
|
||||||
}
|
}
|
||||||
@@ -222,11 +292,41 @@ pub fn json_type(value: &OwnedValue, path: Option<&OwnedValue>) -> crate::Result
|
|||||||
|
|
||||||
/// Returns the value at the given JSON path. If the path does not exist, it returns None.
|
/// Returns the value at the given JSON path. If the path does not exist, it returns None.
|
||||||
/// If the path is an invalid path, returns an error.
|
/// If the path is an invalid path, returns an error.
|
||||||
fn json_extract_single<'a>(json: &'a Val, path: &OwnedValue) -> crate::Result<Option<&'a Val>> {
|
///
|
||||||
let json_path = match path {
|
/// *strict* - if false, we will try to resolve the path even if it does not start with "$"
|
||||||
OwnedValue::Text(t) => json_path(t.value.as_str())?,
|
/// in a way that's compatible with the `->` and `->>` operators. See examples in the docs:
|
||||||
OwnedValue::Null => return Ok(None),
|
/// https://sqlite.org/json1.html#the_and_operators
|
||||||
_ => crate::bail_constraint_error!("JSON path error near: {:?}", path.to_string()),
|
fn json_extract_single<'a>(
|
||||||
|
json: &'a Val,
|
||||||
|
path: &OwnedValue,
|
||||||
|
strict: bool,
|
||||||
|
) -> crate::Result<Option<&'a Val>> {
|
||||||
|
let json_path = if strict {
|
||||||
|
match path {
|
||||||
|
OwnedValue::Text(t) => json_path(t.value.as_str())?,
|
||||||
|
OwnedValue::Null => return Ok(None),
|
||||||
|
_ => crate::bail_constraint_error!("JSON path error near: {:?}", path.to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match path {
|
||||||
|
OwnedValue::Text(t) => {
|
||||||
|
if t.value.starts_with("$") {
|
||||||
|
json_path(t.value.as_str())?
|
||||||
|
} else {
|
||||||
|
JsonPath {
|
||||||
|
elements: vec![PathElement::Root(), PathElement::Key(t.value.to_string())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OwnedValue::Null => return Ok(None),
|
||||||
|
OwnedValue::Integer(i) => JsonPath {
|
||||||
|
elements: vec![PathElement::Root(), PathElement::ArrayLocator(*i as i32)],
|
||||||
|
},
|
||||||
|
OwnedValue::Float(f) => JsonPath {
|
||||||
|
elements: vec![PathElement::Root(), PathElement::Key(f.to_string())],
|
||||||
|
},
|
||||||
|
_ => crate::bail_constraint_error!("JSON path error near: {:?}", path.to_string()),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut current_element = &Val::Null;
|
let mut current_element = &Val::Null;
|
||||||
|
|||||||
@@ -61,6 +61,74 @@ macro_rules! emit_cmp_insn {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! expect_arguments_exact {
|
||||||
|
(
|
||||||
|
$args:expr,
|
||||||
|
$expected_arguments:expr,
|
||||||
|
$func:ident
|
||||||
|
) => {{
|
||||||
|
let args = if let Some(args) = $args {
|
||||||
|
if args.len() != $expected_arguments {
|
||||||
|
crate::bail_parse_error!(
|
||||||
|
"{} function called with not exactly {} arguments",
|
||||||
|
$func.to_string(),
|
||||||
|
$expected_arguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
args
|
||||||
|
} else {
|
||||||
|
crate::bail_parse_error!("{} function with no arguments", $func.to_string());
|
||||||
|
};
|
||||||
|
|
||||||
|
args
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! expect_arguments_max {
|
||||||
|
(
|
||||||
|
$args:expr,
|
||||||
|
$expected_arguments:expr,
|
||||||
|
$func:ident
|
||||||
|
) => {{
|
||||||
|
let args = if let Some(args) = $args {
|
||||||
|
if args.len() > $expected_arguments {
|
||||||
|
crate::bail_parse_error!(
|
||||||
|
"{} function called with more than {} arguments",
|
||||||
|
$func.to_string(),
|
||||||
|
$expected_arguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
args
|
||||||
|
} else {
|
||||||
|
crate::bail_parse_error!("{} function with no arguments", $func.to_string());
|
||||||
|
};
|
||||||
|
|
||||||
|
args
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! expect_arguments_min {
|
||||||
|
(
|
||||||
|
$args:expr,
|
||||||
|
$expected_arguments:expr,
|
||||||
|
$func:ident
|
||||||
|
) => {{
|
||||||
|
let args = if let Some(args) = $args {
|
||||||
|
if args.len() < $expected_arguments {
|
||||||
|
crate::bail_parse_error!(
|
||||||
|
"{} function with less than {} arguments",
|
||||||
|
$func.to_string(),
|
||||||
|
$expected_arguments
|
||||||
|
);
|
||||||
|
}
|
||||||
|
args
|
||||||
|
} else {
|
||||||
|
crate::bail_parse_error!("{} function with no arguments", $func.to_string());
|
||||||
|
};
|
||||||
|
args
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn translate_condition_expr(
|
pub fn translate_condition_expr(
|
||||||
program: &mut ProgramBuilder,
|
program: &mut ProgramBuilder,
|
||||||
referenced_tables: &[TableReference],
|
referenced_tables: &[TableReference],
|
||||||
@@ -413,9 +481,10 @@ pub fn translate_expr(
|
|||||||
match expr {
|
match expr {
|
||||||
ast::Expr::Between { .. } => todo!(),
|
ast::Expr::Between { .. } => todo!(),
|
||||||
ast::Expr::Binary(e1, op, e2) => {
|
ast::Expr::Binary(e1, op, e2) => {
|
||||||
let e1_reg = program.alloc_register();
|
let e1_reg = program.alloc_registers(2);
|
||||||
|
let e2_reg = e1_reg + 1;
|
||||||
|
|
||||||
translate_expr(program, referenced_tables, e1, e1_reg, resolver)?;
|
translate_expr(program, referenced_tables, e1, e1_reg, resolver)?;
|
||||||
let e2_reg = program.alloc_register();
|
|
||||||
translate_expr(program, referenced_tables, e2, e2_reg, resolver)?;
|
translate_expr(program, referenced_tables, e2, e2_reg, resolver)?;
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
@@ -546,6 +615,24 @@ pub fn translate_expr(
|
|||||||
dest: target_register,
|
dest: target_register,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
op @ (ast::Operator::ArrowRight | ast::Operator::ArrowRightShift) => {
|
||||||
|
let json_func = match op {
|
||||||
|
ast::Operator::ArrowRight => JsonFunc::JsonArrowExtract,
|
||||||
|
ast::Operator::ArrowRightShift => JsonFunc::JsonArrowShiftExtract,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
program.emit_insn(Insn::Function {
|
||||||
|
constant_mask: 0,
|
||||||
|
start_reg: e1_reg,
|
||||||
|
dest: target_register,
|
||||||
|
func: FuncCtx {
|
||||||
|
func: Func::Json(json_func),
|
||||||
|
arg_count: 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
other_unimplemented => todo!("{:?}", other_unimplemented),
|
other_unimplemented => todo!("{:?}", other_unimplemented),
|
||||||
}
|
}
|
||||||
Ok(target_register)
|
Ok(target_register)
|
||||||
@@ -689,100 +776,41 @@ pub fn translate_expr(
|
|||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
Func::Json(j) => match j {
|
Func::Json(j) => match j {
|
||||||
JsonFunc::Json => {
|
JsonFunc::Json => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_exact!(args, 1, j);
|
||||||
if args.len() != 1 {
|
|
||||||
crate::bail_parse_error!(
|
translate_function(
|
||||||
"{} function with not exactly 1 argument",
|
|
||||||
j.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with no arguments",
|
|
||||||
j.to_string()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
let regs = program.alloc_register();
|
|
||||||
translate_expr(program, referenced_tables, &args[0], regs, resolver)?;
|
|
||||||
program.emit_insn(Insn::Function {
|
|
||||||
constant_mask: 0,
|
|
||||||
start_reg: regs,
|
|
||||||
dest: target_register,
|
|
||||||
func: func_ctx,
|
|
||||||
});
|
|
||||||
Ok(target_register)
|
|
||||||
}
|
|
||||||
JsonFunc::JsonArray => {
|
|
||||||
let start_reg = translate_variable_sized_function_parameter_list(
|
|
||||||
program,
|
program,
|
||||||
args,
|
args,
|
||||||
referenced_tables,
|
referenced_tables,
|
||||||
resolver,
|
resolver,
|
||||||
)?;
|
target_register,
|
||||||
|
func_ctx,
|
||||||
program.emit_insn(Insn::Function {
|
)
|
||||||
constant_mask: 0,
|
|
||||||
start_reg,
|
|
||||||
dest: target_register,
|
|
||||||
func: func_ctx,
|
|
||||||
});
|
|
||||||
Ok(target_register)
|
|
||||||
}
|
}
|
||||||
JsonFunc::JsonExtract => {
|
JsonFunc::JsonArray | JsonFunc::JsonExtract => translate_function(
|
||||||
let start_reg = translate_variable_sized_function_parameter_list(
|
program,
|
||||||
program,
|
args.as_deref().unwrap_or_default(),
|
||||||
args,
|
referenced_tables,
|
||||||
referenced_tables,
|
resolver,
|
||||||
resolver,
|
target_register,
|
||||||
)?;
|
func_ctx,
|
||||||
|
),
|
||||||
program.emit_insn(Insn::Function {
|
JsonFunc::JsonArrowExtract | JsonFunc::JsonArrowShiftExtract => {
|
||||||
constant_mask: 0,
|
unreachable!(
|
||||||
start_reg,
|
"These two functions are only reachable via the -> and ->> operators"
|
||||||
dest: target_register,
|
)
|
||||||
func: func_ctx,
|
|
||||||
});
|
|
||||||
Ok(target_register)
|
|
||||||
}
|
}
|
||||||
JsonFunc::JsonArrayLength | JsonFunc::JsonType => {
|
JsonFunc::JsonArrayLength | JsonFunc::JsonType => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_max!(args, 2, j);
|
||||||
if args.len() > 2 {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with wrong number of arguments",
|
|
||||||
j.to_string()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with no arguments",
|
|
||||||
j.to_string()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let json_reg = program.alloc_register();
|
translate_function(
|
||||||
let path_reg = program.alloc_register();
|
program,
|
||||||
|
args,
|
||||||
translate_expr(program, referenced_tables, &args[0], json_reg, resolver)?;
|
referenced_tables,
|
||||||
|
resolver,
|
||||||
if args.len() == 2 {
|
target_register,
|
||||||
translate_expr(
|
func_ctx,
|
||||||
program,
|
)
|
||||||
referenced_tables,
|
|
||||||
&args[1],
|
|
||||||
path_reg,
|
|
||||||
resolver,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
program.emit_insn(Insn::Function {
|
|
||||||
constant_mask: 0,
|
|
||||||
start_reg: json_reg,
|
|
||||||
dest: target_register,
|
|
||||||
func: func_ctx,
|
|
||||||
});
|
|
||||||
Ok(target_register)
|
|
||||||
}
|
}
|
||||||
JsonFunc::JsonErrorPosition => {
|
JsonFunc::JsonErrorPosition => {
|
||||||
let args = if let Some(args) = args {
|
let args = if let Some(args) = args {
|
||||||
@@ -831,37 +859,16 @@ pub fn translate_expr(
|
|||||||
});
|
});
|
||||||
Ok(target_register)
|
Ok(target_register)
|
||||||
}
|
}
|
||||||
ScalarFunc::Char => {
|
ScalarFunc::Char => translate_function(
|
||||||
let start_reg = translate_variable_sized_function_parameter_list(
|
program,
|
||||||
program,
|
args.as_deref().unwrap_or_default(),
|
||||||
args,
|
referenced_tables,
|
||||||
referenced_tables,
|
resolver,
|
||||||
resolver,
|
target_register,
|
||||||
)?;
|
func_ctx,
|
||||||
|
),
|
||||||
program.emit_insn(Insn::Function {
|
|
||||||
constant_mask: 0,
|
|
||||||
start_reg,
|
|
||||||
dest: target_register,
|
|
||||||
func: func_ctx,
|
|
||||||
});
|
|
||||||
Ok(target_register)
|
|
||||||
}
|
|
||||||
ScalarFunc::Coalesce => {
|
ScalarFunc::Coalesce => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_min!(args, 2, srf);
|
||||||
if args.len() < 2 {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with less than 2 arguments",
|
|
||||||
srf.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with no arguments",
|
|
||||||
srf.to_string()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// coalesce function is implemented as a series of not null checks
|
// coalesce function is implemented as a series of not null checks
|
||||||
// whenever a not null check succeeds, we jump to the end of the series
|
// whenever a not null check succeeds, we jump to the end of the series
|
||||||
@@ -919,17 +926,7 @@ pub fn translate_expr(
|
|||||||
Ok(target_register)
|
Ok(target_register)
|
||||||
}
|
}
|
||||||
ScalarFunc::ConcatWs => {
|
ScalarFunc::ConcatWs => {
|
||||||
let args = match args {
|
let args = expect_arguments_min!(args, 2, srf);
|
||||||
Some(args) if args.len() >= 2 => args,
|
|
||||||
Some(_) => crate::bail_parse_error!(
|
|
||||||
"{} function requires at least 2 arguments",
|
|
||||||
srf.to_string()
|
|
||||||
),
|
|
||||||
None => crate::bail_parse_error!(
|
|
||||||
"{} function requires arguments",
|
|
||||||
srf.to_string()
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let temp_register = program.alloc_register();
|
let temp_register = program.alloc_register();
|
||||||
for arg in args.iter() {
|
for arg in args.iter() {
|
||||||
@@ -1076,20 +1073,7 @@ pub fn translate_expr(
|
|||||||
| ScalarFunc::Sign
|
| ScalarFunc::Sign
|
||||||
| ScalarFunc::Soundex
|
| ScalarFunc::Soundex
|
||||||
| ScalarFunc::ZeroBlob => {
|
| ScalarFunc::ZeroBlob => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_exact!(args, 1, srf);
|
||||||
if args.len() != 1 {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with not exactly 1 argument",
|
|
||||||
srf.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with no arguments",
|
|
||||||
srf.to_string()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
let reg =
|
let reg =
|
||||||
translate_and_mark(program, referenced_tables, &args[0], resolver)?;
|
translate_and_mark(program, referenced_tables, &args[0], resolver)?;
|
||||||
program.emit_insn(Insn::Function {
|
program.emit_insn(Insn::Function {
|
||||||
@@ -1275,20 +1259,7 @@ pub fn translate_expr(
|
|||||||
| ScalarFunc::RTrim
|
| ScalarFunc::RTrim
|
||||||
| ScalarFunc::Round
|
| ScalarFunc::Round
|
||||||
| ScalarFunc::Unhex => {
|
| ScalarFunc::Unhex => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_max!(args, 2, srf);
|
||||||
if args.len() > 2 {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with more than 2 arguments",
|
|
||||||
srf.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with no arguments",
|
|
||||||
srf.to_string()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
for arg in args.iter() {
|
for arg in args.iter() {
|
||||||
translate_and_mark(program, referenced_tables, arg, resolver)?;
|
translate_and_mark(program, referenced_tables, arg, resolver)?;
|
||||||
@@ -1461,21 +1432,7 @@ pub fn translate_expr(
|
|||||||
#[cfg(feature = "uuid")]
|
#[cfg(feature = "uuid")]
|
||||||
ExtFunc::Uuid(ref uuid_fn) => match uuid_fn {
|
ExtFunc::Uuid(ref uuid_fn) => match uuid_fn {
|
||||||
UuidFunc::UuidStr | UuidFunc::UuidBlob | UuidFunc::Uuid7TS => {
|
UuidFunc::UuidStr | UuidFunc::UuidBlob | UuidFunc::Uuid7TS => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_exact!(args, 1, ext_func);
|
||||||
if args.len() != 1 {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with not exactly 1 argument",
|
|
||||||
ext_func.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with no arguments",
|
|
||||||
ext_func.to_string()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let regs = program.alloc_register();
|
let regs = program.alloc_register();
|
||||||
translate_expr(program, referenced_tables, &args[0], regs, resolver)?;
|
translate_expr(program, referenced_tables, &args[0], regs, resolver)?;
|
||||||
program.emit_insn(Insn::Function {
|
program.emit_insn(Insn::Function {
|
||||||
@@ -1503,14 +1460,7 @@ pub fn translate_expr(
|
|||||||
Ok(target_register)
|
Ok(target_register)
|
||||||
}
|
}
|
||||||
UuidFunc::Uuid7 => {
|
UuidFunc::Uuid7 => {
|
||||||
let args = match args {
|
let args = expect_arguments_max!(args, 1, ext_func);
|
||||||
Some(args) if args.len() > 1 => crate::bail_parse_error!(
|
|
||||||
"{} function with more than 1 argument",
|
|
||||||
ext_func.to_string()
|
|
||||||
),
|
|
||||||
Some(args) => args,
|
|
||||||
None => &vec![],
|
|
||||||
};
|
|
||||||
let mut start_reg = None;
|
let mut start_reg = None;
|
||||||
if let Some(arg) = args.first() {
|
if let Some(arg) = args.first() {
|
||||||
start_reg = Some(translate_and_mark(
|
start_reg = Some(translate_and_mark(
|
||||||
@@ -1548,18 +1498,7 @@ pub fn translate_expr(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MathFuncArity::Unary => {
|
MathFuncArity::Unary => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_exact!(args, 1, math_func);
|
||||||
if args.len() != 1 {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with not exactly 1 argument",
|
|
||||||
math_func
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!("{} function with no arguments", math_func);
|
|
||||||
};
|
|
||||||
|
|
||||||
let reg =
|
let reg =
|
||||||
translate_and_mark(program, referenced_tables, &args[0], resolver)?;
|
translate_and_mark(program, referenced_tables, &args[0], resolver)?;
|
||||||
program.emit_insn(Insn::Function {
|
program.emit_insn(Insn::Function {
|
||||||
@@ -1572,17 +1511,7 @@ pub fn translate_expr(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MathFuncArity::Binary => {
|
MathFuncArity::Binary => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_exact!(args, 2, math_func);
|
||||||
if args.len() != 2 {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with not exactly 2 arguments",
|
|
||||||
math_func
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!("{} function with no arguments", math_func);
|
|
||||||
};
|
|
||||||
let reg1 = program.alloc_register();
|
let reg1 = program.alloc_register();
|
||||||
let _ =
|
let _ =
|
||||||
translate_expr(program, referenced_tables, &args[0], reg1, resolver)?;
|
translate_expr(program, referenced_tables, &args[0], reg1, resolver)?;
|
||||||
@@ -1599,17 +1528,7 @@ pub fn translate_expr(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MathFuncArity::UnaryOrBinary => {
|
MathFuncArity::UnaryOrBinary => {
|
||||||
let args = if let Some(args) = args {
|
let args = expect_arguments_max!(args, 2, math_func);
|
||||||
if args.len() > 2 {
|
|
||||||
crate::bail_parse_error!(
|
|
||||||
"{} function with more than 2 arguments",
|
|
||||||
math_func
|
|
||||||
);
|
|
||||||
}
|
|
||||||
args
|
|
||||||
} else {
|
|
||||||
crate::bail_parse_error!("{} function with no arguments", math_func);
|
|
||||||
};
|
|
||||||
|
|
||||||
let regs = program.alloc_registers(args.len());
|
let regs = program.alloc_registers(args.len());
|
||||||
for (i, arg) in args.iter().enumerate() {
|
for (i, arg) in args.iter().enumerate() {
|
||||||
@@ -1843,25 +1762,33 @@ pub fn translate_expr(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the starting register for the function.
|
/// Emits a whole insn for a function call.
|
||||||
// TODO: Use this function for all functions with variable number of parameters in `translate_expr`
|
/// Assumes the number of parameters is valid for the given function.
|
||||||
fn translate_variable_sized_function_parameter_list(
|
/// Returns the target register for the function.
|
||||||
|
fn translate_function(
|
||||||
program: &mut ProgramBuilder,
|
program: &mut ProgramBuilder,
|
||||||
args: &Option<Vec<ast::Expr>>,
|
args: &[ast::Expr],
|
||||||
referenced_tables: Option<&[TableReference]>,
|
referenced_tables: Option<&[TableReference]>,
|
||||||
resolver: &Resolver,
|
resolver: &Resolver,
|
||||||
|
target_register: usize,
|
||||||
|
func_ctx: FuncCtx,
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
let args = args.as_deref().unwrap_or_default();
|
let start_reg = program.alloc_registers(args.len());
|
||||||
|
let mut current_reg = start_reg;
|
||||||
let reg = program.alloc_registers(args.len());
|
|
||||||
let mut current_reg = reg;
|
|
||||||
|
|
||||||
for arg in args.iter() {
|
for arg in args.iter() {
|
||||||
translate_expr(program, referenced_tables, arg, current_reg, resolver)?;
|
translate_expr(program, referenced_tables, arg, current_reg, resolver)?;
|
||||||
current_reg += 1;
|
current_reg += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(reg)
|
program.emit_insn(Insn::Function {
|
||||||
|
constant_mask: 0,
|
||||||
|
start_reg,
|
||||||
|
dest: target_register,
|
||||||
|
func: func_ctx,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(target_register)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_eval_jump_expr(
|
fn wrap_eval_jump_expr(
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ use crate::util::parse_schema_rows;
|
|||||||
use crate::vdbe::insn::Insn;
|
use crate::vdbe::insn::Insn;
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
use crate::{
|
use crate::{
|
||||||
function::JsonFunc,
|
function::JsonFunc, json::get_json, json::json_array, json::json_array_length,
|
||||||
json::{get_json, json_array, json_array_length, json_error_position, json_extract, json_type},
|
json::json_arrow_extract, json::json_arrow_shift_extract, json::json_extract, json::json_type,
|
||||||
|
json::json_error_position,
|
||||||
};
|
};
|
||||||
use crate::{Connection, Result, Rows, TransactionState, DATABASE_VERSION};
|
use crate::{Connection, Result, Rows, TransactionState, DATABASE_VERSION};
|
||||||
use datetime::{exec_date, exec_datetime_full, exec_julianday, exec_time, exec_unixepoch};
|
use datetime::{exec_date, exec_datetime_full, exec_julianday, exec_time, exec_unixepoch};
|
||||||
@@ -1381,6 +1382,24 @@ impl Program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
|
crate::function::Func::Json(
|
||||||
|
func @ (JsonFunc::JsonArrowExtract | JsonFunc::JsonArrowShiftExtract),
|
||||||
|
) => {
|
||||||
|
assert_eq!(arg_count, 2);
|
||||||
|
let json = &state.registers[*start_reg];
|
||||||
|
let path = &state.registers[*start_reg + 1];
|
||||||
|
let func = match func {
|
||||||
|
JsonFunc::JsonArrowExtract => json_arrow_extract,
|
||||||
|
JsonFunc::JsonArrowShiftExtract => json_arrow_shift_extract,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let json_str = func(json, path);
|
||||||
|
match json_str {
|
||||||
|
Ok(json) => state.registers[*dest] = json,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "json")]
|
||||||
crate::function::Func::Json(
|
crate::function::Func::Json(
|
||||||
func @ (JsonFunc::JsonArrayLength | JsonFunc::JsonType),
|
func @ (JsonFunc::JsonArrayLength | JsonFunc::JsonType),
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@@ -89,6 +89,18 @@ do_execsql_test json_extract_null {
|
|||||||
SELECT json_extract(null, '$')
|
SELECT json_extract(null, '$')
|
||||||
} {{}}
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_json_null_type {
|
||||||
|
SELECT typeof(json_extract('null', '$'))
|
||||||
|
} {{null}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_json_null_type {
|
||||||
|
SELECT typeof('null' -> '$')
|
||||||
|
} {{text}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_json_null_type {
|
||||||
|
SELECT typeof('null' ->> '$')
|
||||||
|
} {{null}}
|
||||||
|
|
||||||
do_execsql_test json_extract_empty {
|
do_execsql_test json_extract_empty {
|
||||||
SELECT json_extract()
|
SELECT json_extract()
|
||||||
} {{}}
|
} {{}}
|
||||||
@@ -113,10 +125,38 @@ do_execsql_test json_extract_number {
|
|||||||
SELECT json_extract(1, '$')
|
SELECT json_extract(1, '$')
|
||||||
} {{1}}
|
} {{1}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_number_type {
|
||||||
|
SELECT typeof(json_extract(1, '$'))
|
||||||
|
} {{integer}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_number {
|
||||||
|
SELECT 1 -> '$'
|
||||||
|
} {{1}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_number_type {
|
||||||
|
SELECT typeof(1 -> '$')
|
||||||
|
} {{text}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_number {
|
||||||
|
SELECT 1 -> '$'
|
||||||
|
} {{1}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_number_type {
|
||||||
|
SELECT typeof(1 ->> '$')
|
||||||
|
} {{integer}}
|
||||||
|
|
||||||
do_execsql_test json_extract_object_1 {
|
do_execsql_test json_extract_object_1 {
|
||||||
SELECT json_extract('{"a": [1,2,3]}', '$.a')
|
SELECT json_extract('{"a": [1,2,3]}', '$.a')
|
||||||
} {{[1,2,3]}}
|
} {{[1,2,3]}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_object {
|
||||||
|
SELECT '{"a": [1,2,3]}' -> '$.a'
|
||||||
|
} {{[1,2,3]}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_object {
|
||||||
|
SELECT '{"a": [1,2,3]}' ->> '$.a'
|
||||||
|
} {{[1,2,3]}}
|
||||||
|
|
||||||
do_execsql_test json_extract_object_2 {
|
do_execsql_test json_extract_object_2 {
|
||||||
SELECT json_extract('{"a": [1,2,3]}', '$.a', '$.a[0]', '$.a[1]', '$.a[3]')
|
SELECT json_extract('{"a": [1,2,3]}', '$.a', '$.a[0]', '$.a[1]', '$.a[3]')
|
||||||
} {{[[1,2,3],1,2,null]}}
|
} {{[[1,2,3],1,2,null]}}
|
||||||
@@ -140,11 +180,176 @@ do_execsql_test json_extract_null_path {
|
|||||||
SELECT json_extract(1, null)
|
SELECT json_extract(1, null)
|
||||||
} {{}}
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_null_path {
|
||||||
|
SELECT 1 -> null
|
||||||
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_null_path {
|
||||||
|
SELECT 1 ->> null
|
||||||
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_float {
|
||||||
|
SELECT typeof(json_extract(1.0, '$'))
|
||||||
|
} {{real}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_float {
|
||||||
|
SELECT typeof(1.0 -> '$')
|
||||||
|
} {{text}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_float {
|
||||||
|
SELECT typeof(1.0 ->> '$')
|
||||||
|
} {{real}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_true {
|
||||||
|
SELECT json_extract('true', '$')
|
||||||
|
} {{1}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_true_type {
|
||||||
|
SELECT typeof(json_extract('true', '$'))
|
||||||
|
} {{integer}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_true {
|
||||||
|
SELECT 'true' -> '$'
|
||||||
|
} {{true}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_true_type {
|
||||||
|
SELECT typeof('true' -> '$')
|
||||||
|
} {{text}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_true {
|
||||||
|
SELECT 'true' ->> '$'
|
||||||
|
} {{1}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_true_type {
|
||||||
|
SELECT typeof('true' ->> '$')
|
||||||
|
} {{integer}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_false {
|
||||||
|
SELECT json_extract('false', '$')
|
||||||
|
} {{0}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_false_type {
|
||||||
|
SELECT typeof(json_extract('false', '$'))
|
||||||
|
} {{integer}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_false {
|
||||||
|
SELECT 'false' -> '$'
|
||||||
|
} {{false}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_false_type {
|
||||||
|
SELECT typeof('false' -> '$')
|
||||||
|
} {{text}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_false {
|
||||||
|
SELECT 'false' ->> '$'
|
||||||
|
} {{0}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_false_type {
|
||||||
|
SELECT typeof('false' ->> '$')
|
||||||
|
} {{integer}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_string {
|
||||||
|
SELECT json_extract('"string"', '$')
|
||||||
|
} {{string}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_string_type {
|
||||||
|
SELECT typeof(json_extract('"string"', '$'))
|
||||||
|
} {{text}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_string {
|
||||||
|
SELECT '"string"' -> '$'
|
||||||
|
} {{"string"}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_string_type {
|
||||||
|
SELECT typeof('"string"' -> '$')
|
||||||
|
} {{text}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_string {
|
||||||
|
SELECT '"string"' ->> '$'
|
||||||
|
} {{string}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_string_type {
|
||||||
|
SELECT typeof('"string"' ->> '$')
|
||||||
|
} {{text}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_implicit_root_path {
|
||||||
|
SELECT '{"a":1}' -> 'a';
|
||||||
|
} {{1}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_implicit_root_path {
|
||||||
|
SELECT '{"a":1}' ->> 'a';
|
||||||
|
} {{1}}
|
||||||
|
|
||||||
|
# TODO: fix me after rebasing on top of https://github.com/tursodatabase/limbo/pull/631 - use the Option value in json_extract_single
|
||||||
|
#do_execsql_test json_arrow_implicit_root_path_undefined_key {
|
||||||
|
# SELECT '{"a":1}' -> 'x';
|
||||||
|
#} {{}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_implicit_root_path_undefined_key {
|
||||||
|
SELECT '{"a":1}' ->> 'x';
|
||||||
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_implicit_root_path_array {
|
||||||
|
SELECT '[1,2,3]' -> 1;
|
||||||
|
} {{2}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_implicit_root_path_array {
|
||||||
|
SELECT '[1,2,3]' ->> 1;
|
||||||
|
} {{2}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_implicit_root_path_array_negative_idx {
|
||||||
|
SELECT '[1,2,3]' -> -1;
|
||||||
|
} {{3}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_implicit_root_path_array_negative_idx {
|
||||||
|
SELECT '[1,2,3]' ->> -1;
|
||||||
|
} {{3}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_implicit_real_cast {
|
||||||
|
SELECT '{"1.5":"abc"}' -> 1.5;
|
||||||
|
} {{"abc"}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_implicit_real_cast {
|
||||||
|
SELECT '{"1.5":"abc"}' -> 1.5;
|
||||||
|
} {{"abc"}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_implicit_true_cast {
|
||||||
|
SELECT '[1,2,3]' -> true
|
||||||
|
} {{2}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_implicit_true_cast {
|
||||||
|
SELECT '[1,2,3]' ->> true
|
||||||
|
} {{2}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_implicit_false_cast {
|
||||||
|
SELECT '[1,2,3]' -> false
|
||||||
|
} {{1}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_implicit_false_cast {
|
||||||
|
SELECT '[1,2,3]' ->> false
|
||||||
|
} {{1}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_chained {
|
||||||
|
select '{"a":2,"c":[4,5,{"f":7}]}' -> 'c' -> 2 ->> 'f'
|
||||||
|
} {{7}}
|
||||||
|
|
||||||
# TODO: fix me - this passes on SQLite and needs to be fixed in Limbo.
|
# TODO: fix me - this passes on SQLite and needs to be fixed in Limbo.
|
||||||
do_execsql_test json_extract_multiple_null_paths {
|
do_execsql_test json_extract_multiple_null_paths {
|
||||||
SELECT json_extract(1, null, null, null)
|
SELECT json_extract(1, null, null, null)
|
||||||
} {{}}
|
} {{}}
|
||||||
|
|
||||||
|
do_execsql_test json_extract_array {
|
||||||
|
SELECT json_extract('[1,2,3]', '$')
|
||||||
|
} {{[1,2,3]}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_array {
|
||||||
|
SELECT '[1,2,3]' -> '$'
|
||||||
|
} {{[1,2,3]}}
|
||||||
|
|
||||||
|
do_execsql_test json_arrow_shift_array {
|
||||||
|
SELECT '[1,2,3]' ->> '$'
|
||||||
|
} {{[1,2,3]}}
|
||||||
|
|
||||||
# TODO: fix me - this passes on SQLite and needs to be fixed in Limbo.
|
# TODO: fix me - this passes on SQLite and needs to be fixed in Limbo.
|
||||||
#do_execsql_test json_extract_quote {
|
#do_execsql_test json_extract_quote {
|
||||||
# SELECT json_extract('{"\"":1 }', '$.\"')
|
# SELECT json_extract('{"\"":1 }', '$.\"')
|
||||||
|
|||||||
@@ -253,10 +253,10 @@ def test_import_csv(test_name: str, options: str, import_output: str, table_outp
|
|||||||
table_output,
|
table_output,
|
||||||
)
|
)
|
||||||
|
|
||||||
test_import_csv('no_options', '--csv', '', '1|2.0|String1\n3|4.0|String2')
|
test_import_csv('no_options', '--csv', '', '1|2.0|String\'1\n3|4.0|String2')
|
||||||
test_import_csv('verbose', '--csv -v',
|
test_import_csv('verbose', '--csv -v',
|
||||||
'Added 2 rows with 0 errors using 2 lines of input'
|
'Added 2 rows with 0 errors using 2 lines of input'
|
||||||
,'1|2.0|String1\n3|4.0|String2')
|
,'1|2.0|String\'1\n3|4.0|String2')
|
||||||
test_import_csv('skip', '--csv --skip 1', '' ,'3|4.0|String2')
|
test_import_csv('skip', '--csv --skip 1', '' ,'3|4.0|String2')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
1,2.0,"String1"
|
1,2.0,"String'1"
|
||||||
3,4.0,"String2"
|
3,4.0,"String2"
|
||||||
|
Reference in New Issue
Block a user