From 96d459ad8feaa7e30331477a51bcc4b634790011 Mon Sep 17 00:00:00 2001 From: JasonShin Date: Wed, 4 Mar 2026 22:36:32 +1100 Subject: [PATCH 01/19] Support JSON --- .../sql_parser/expressions/functions.rs | 24 ++ .../sql_parser/expressions/translate_expr.rs | 287 +++++++++++++++++- src/ts_generator/types/ts_query.rs | 16 + .../demo/postgres/jsonb_operations.queries.ts | 4 +- .../postgres/jsonb_operations.snapshot.ts | 4 +- tests/demo/select/no-default-table.queries.ts | 4 +- .../demo/select/no-default-table.snapshot.ts | 4 +- tests/sample/sample.queries.ts | 44 --- 8 files changed, 334 insertions(+), 53 deletions(-) delete mode 100644 tests/sample/sample.queries.ts diff --git a/src/ts_generator/sql_parser/expressions/functions.rs b/src/ts_generator/sql_parser/expressions/functions.rs index fb8d2bd4..51f2397d 100644 --- a/src/ts_generator/sql_parser/expressions/functions.rs +++ b/src/ts_generator/sql_parser/expressions/functions.rs @@ -170,3 +170,27 @@ pub static TYPE_POLYMORPHIC_FUNCTIONS: &[&str] = &[ pub fn is_type_polymorphic_function(func_name: &str) -> bool { TYPE_POLYMORPHIC_FUNCTIONS.contains(&func_name.to_uppercase().as_str()) } + +// JSON/JSONB functions that build objects/arrays +pub static JSON_BUILD_FUNCTIONS: &[&str] = &[ + "JSONB_BUILD_OBJECT", + "JSON_BUILD_OBJECT", + "JSONB_BUILD_ARRAY", + "JSON_BUILD_ARRAY", +]; + +// JSON/JSONB aggregation functions +pub static JSON_AGG_FUNCTIONS: &[&str] = &[ + "JSONB_AGG", + "JSON_AGG", + "JSON_OBJECT_AGG", + "JSONB_OBJECT_AGG", +]; + +pub fn is_json_build_function(func_name: &str) -> bool { + JSON_BUILD_FUNCTIONS.contains(&func_name.to_uppercase().as_str()) +} + +pub fn is_json_agg_function(func_name: &str) -> bool { + JSON_AGG_FUNCTIONS.contains(&func_name.to_uppercase().as_str()) +} diff --git a/src/ts_generator/sql_parser/expressions/translate_expr.rs b/src/ts_generator/sql_parser/expressions/translate_expr.rs index d47ea135..a7fea063 100644 --- a/src/ts_generator/sql_parser/expressions/translate_expr.rs +++ b/src/ts_generator/sql_parser/expressions/translate_expr.rs @@ -1,4 +1,4 @@ -use super::functions::{is_date_function, is_numeric_function, is_type_polymorphic_function}; +use super::functions::{is_date_function, is_json_agg_function, is_json_build_function, is_numeric_function, is_type_polymorphic_function}; use crate::common::lazy::DB_SCHEMA; use crate::common::logger::{error, warning}; use crate::core::connection::DBConn; @@ -707,6 +707,291 @@ pub async fn translate_expr( return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); } + // Handle JSON build functions (jsonb_build_object, json_build_object, etc.) + if is_json_build_function(function_name_str) { + use sqlparser::ast::{FunctionArg, FunctionArgExpr, FunctionArguments}; + + let args = match &func_obj.args { + FunctionArguments::List(arg_list) => &arg_list.args, + _ => { + // If no arguments or subquery, return Any + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + }; + + // jsonb_build_object takes key-value pairs + // e.g., jsonb_build_object('id', id, 'name', name) + if function_name_str.to_uppercase() == "JSONB_BUILD_OBJECT" || function_name_str.to_uppercase() == "JSON_BUILD_OBJECT" { + if args.len() % 2 != 0 { + // Invalid number of arguments + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + + let mut object_fields = vec![]; + + // Process key-value pairs + for i in (0..args.len()).step_by(2) { + let key_arg = &args[i]; + let value_arg = &args[i + 1]; + + // Extract key name (should be a string literal) + let key_name = match key_arg { + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(val))) => { + match &val.value { + Value::SingleQuotedString(s) | Value::DoubleQuotedString(s) => Some(s.clone()), + _ => None, + } + } + _ => None, + }; + + if key_name.is_none() { + // If we can't extract the key, return Any + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + + let key_name = key_name.unwrap(); + + // Extract value type from the expression + let value_expr = match value_arg { + FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), + FunctionArg::Named { + arg: FunctionArgExpr::Expr(expr), + .. + } => Some(expr), + _ => None, + }; + + if let Some(value_expr) = value_expr { + let value_type = match value_expr { + Expr::Identifier(ident) => { + let column_name = DisplayIndent(ident).to_string(); + if let Some(table_name) = single_table_name { + let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], db_conn).await; + + if let Some(table_details) = table_details { + if let Some(field) = table_details.get(&column_name) { + Some((field.field_type.to_owned(), field.is_nullable)) + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } + Expr::CompoundIdentifier(idents) if idents.len() == 2 => { + let column_name = DisplayIndent(&idents[1]).to_string(); + if let Ok(table_name) = translate_table_from_expr(table_with_joins, value_expr) { + let table_details = &DB_SCHEMA + .lock() + .await + .fetch_table(&vec![table_name.as_str()], db_conn) + .await; + + if let Some(table_details) = table_details { + if let Some(field) = table_details.get(&column_name) { + Some((field.field_type.to_owned(), field.is_nullable)) + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } + Expr::Value(val) => { + if let Some(ts_field_type) = translate_value(&val.value) { + Some((ts_field_type, false)) + } else { + Some((TsFieldType::Any, false)) + } + } + _ => Some((TsFieldType::Any, false)), + }; + + if let Some((value_type, is_nullable)) = value_type { + object_fields.push((key_name, value_type, is_nullable)); + } else { + // If we can't infer the type, return Any + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + } else { + // If we can't extract the value expression, return Any + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + } + + // Build the StructuredObject type with the inferred fields + let object_type = TsFieldType::StructuredObject(object_fields); + return ts_query.insert_result(Some(alias), &[object_type], is_selection, false, expr_for_logging); + } + + // For build_array functions, we'd need different logic + // For now, return Any + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + + // Handle JSON aggregation functions (jsonb_agg, json_agg, etc.) + if is_json_agg_function(function_name_str) { + use sqlparser::ast::{FunctionArg, FunctionArgExpr, FunctionArguments}; + + let args = match &func_obj.args { + FunctionArguments::List(arg_list) => &arg_list.args, + _ => { + // If no arguments or subquery, return Any + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + }; + + // jsonb_agg typically takes a single expression + // e.g., jsonb_agg(jsonb_build_object('id', id, 'name', name)) + if args.len() == 1 { + let first_arg = &args[0]; + let arg_expr = match first_arg { + FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), + FunctionArg::Named { + arg: FunctionArgExpr::Expr(expr), + .. + } => Some(expr), + _ => None, + }; + + if let Some(arg_expr) = arg_expr { + // Check if the argument is a jsonb_build_object function + if let Expr::Function(inner_func) = arg_expr { + let inner_func_name = inner_func.name.to_string(); + if is_json_build_function(inner_func_name.as_str()) { + // Recursively infer the type of jsonb_build_object + let inner_args = match &inner_func.args { + FunctionArguments::List(arg_list) => &arg_list.args, + _ => { + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + }; + + if inner_args.len() % 2 != 0 { + // Invalid number of arguments + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + + let mut object_fields = vec![]; + + // Process key-value pairs + for i in (0..inner_args.len()).step_by(2) { + let key_arg = &inner_args[i]; + let value_arg = &inner_args[i + 1]; + + // Extract key name + let key_name = match key_arg { + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(val))) => { + match &val.value { + Value::SingleQuotedString(s) | Value::DoubleQuotedString(s) => Some(s.clone()), + _ => None, + } + } + _ => None, + }; + + if key_name.is_none() { + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + + let key_name = key_name.unwrap(); + + // Extract value type + let value_expr = match value_arg { + FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), + FunctionArg::Named { + arg: FunctionArgExpr::Expr(expr), + .. + } => Some(expr), + _ => None, + }; + + if let Some(value_expr) = value_expr { + let value_type = match value_expr { + Expr::Identifier(ident) => { + let column_name = DisplayIndent(ident).to_string(); + if let Some(table_name) = single_table_name { + let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], db_conn).await; + + if let Some(table_details) = table_details { + if let Some(field) = table_details.get(&column_name) { + Some((field.field_type.to_owned(), field.is_nullable)) + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } + Expr::CompoundIdentifier(idents) if idents.len() == 2 => { + let column_name = DisplayIndent(&idents[1]).to_string(); + if let Ok(table_name) = translate_table_from_expr(table_with_joins, value_expr) { + let table_details = &DB_SCHEMA + .lock() + .await + .fetch_table(&vec![table_name.as_str()], db_conn) + .await; + + if let Some(table_details) = table_details { + if let Some(field) = table_details.get(&column_name) { + Some((field.field_type.to_owned(), field.is_nullable)) + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } + Expr::Value(val) => { + if let Some(ts_field_type) = translate_value(&val.value) { + Some((ts_field_type, false)) + } else { + Some((TsFieldType::Any, false)) + } + } + _ => Some((TsFieldType::Any, false)), + }; + + if let Some((value_type, is_nullable)) = value_type { + object_fields.push((key_name, value_type, is_nullable)); + } else { + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + } else { + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + } + + // Build the Array of StructuredObject type + let object_type = TsFieldType::StructuredObject(object_fields); + let array_type = TsFieldType::Array(Box::new(object_type)); + return ts_query.insert_result(Some(alias), &[array_type], is_selection, false, expr_for_logging); + } + } + + // If not jsonb_build_object, try to infer the type of the expression + // For now, return Any + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + } + + // If we can't infer the type, return Any + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + } + // Handle other function types if is_string_function(function_name_str) { ts_query.insert_result( diff --git a/src/ts_generator/types/ts_query.rs b/src/ts_generator/types/ts_query.rs index b31797be..dc5159e4 100644 --- a/src/ts_generator/types/ts_query.rs +++ b/src/ts_generator/types/ts_query.rs @@ -16,6 +16,8 @@ pub enum TsFieldType { Number, Boolean, Object, + // Structured object with named fields: Vec<(field_name, field_type, is_nullable)> + StructuredObject(Vec<(String, TsFieldType, bool)>), Date, Null, Enum(Vec), @@ -34,6 +36,20 @@ impl fmt::Display for TsFieldType { TsFieldType::Number => write!(f, "number"), TsFieldType::String => write!(f, "string"), TsFieldType::Object => write!(f, "object"), + TsFieldType::StructuredObject(fields) => { + let field_strings: Vec = fields + .iter() + .map(|(field_name, field_type, is_nullable)| { + let type_str = if *is_nullable { + format!("{} | null", field_type) + } else { + field_type.to_string() + }; + format!("{}: {}", field_name, type_str) + }) + .collect(); + write!(f, "{{ {} }}", field_strings.join("; ")) + } TsFieldType::Date => write!(f, "Date"), TsFieldType::Any => write!(f, "any"), TsFieldType::Null => write!(f, "null"), diff --git a/tests/demo/postgres/jsonb_operations.queries.ts b/tests/demo/postgres/jsonb_operations.queries.ts index 42ab19cd..01832bba 100644 --- a/tests/demo/postgres/jsonb_operations.queries.ts +++ b/tests/demo/postgres/jsonb_operations.queries.ts @@ -2,7 +2,7 @@ export type JsonbBuildObjectBasicParams = []; export interface IJsonbBuildObjectBasicResult { id: number; - itemJson: any; + itemJson: { id: number; name: string; rarity: string | null }; } export interface IJsonbBuildObjectBasicQuery { @@ -13,7 +13,7 @@ export interface IJsonbBuildObjectBasicQuery { export type JsonbAggregationParams = []; export interface IJsonbAggregationResult { - items: any; + items: Array<{ id: number; name: string }>; rarity: string | null; } diff --git a/tests/demo/postgres/jsonb_operations.snapshot.ts b/tests/demo/postgres/jsonb_operations.snapshot.ts index ac767890..6375de99 100644 --- a/tests/demo/postgres/jsonb_operations.snapshot.ts +++ b/tests/demo/postgres/jsonb_operations.snapshot.ts @@ -2,7 +2,7 @@ export type JsonbBuildObjectBasicParams = []; export interface IJsonbBuildObjectBasicResult { id: number; - itemJson: any; + itemJson: { id: number; name: string; rarity: string | null }; } export interface IJsonbBuildObjectBasicQuery { @@ -13,7 +13,7 @@ export interface IJsonbBuildObjectBasicQuery { export type JsonbAggregationParams = []; export interface IJsonbAggregationResult { - items: any; + items: Array<{ id: number; name: string }>; rarity: string | null; } diff --git a/tests/demo/select/no-default-table.queries.ts b/tests/demo/select/no-default-table.queries.ts index 7ae64d07..f78500bb 100644 --- a/tests/demo/select/no-default-table.queries.ts +++ b/tests/demo/select/no-default-table.queries.ts @@ -70,10 +70,10 @@ export interface IAllStringsResult { extractSecond1: Date; extractYear1: Date; jsonArray1: any; - jsonBuildObject1: any; + jsonBuildObject1: { key: string }; jsonExtractPathText1: string; jsonbArray1: any; - jsonbBuildObject1: any; + jsonbBuildObject1: { key: string }; jsonbExtractPathText1: string; left1: string; length1: string; diff --git a/tests/demo/select/no-default-table.snapshot.ts b/tests/demo/select/no-default-table.snapshot.ts index dbc129c7..dc0b75dd 100644 --- a/tests/demo/select/no-default-table.snapshot.ts +++ b/tests/demo/select/no-default-table.snapshot.ts @@ -70,10 +70,10 @@ export interface IAllStringsResult { extractSecond1: Date; extractYear1: Date; jsonArray1: any; - jsonBuildObject1: any; + jsonBuildObject1: { key: string }; jsonExtractPathText1: string; jsonbArray1: any; - jsonbBuildObject1: any; + jsonbBuildObject1: { key: string }; jsonbExtractPathText1: string; left1: string; length1: string; diff --git a/tests/sample/sample.queries.ts b/tests/sample/sample.queries.ts deleted file mode 100644 index f29c73b5..00000000 --- a/tests/sample/sample.queries.ts +++ /dev/null @@ -1,44 +0,0 @@ -export type SampleSelectQueryParams = [number]; - -export interface ISampleSelectQueryResult { - name: string; - some_id: number; -} - -export interface ISampleSelectQueryQuery { - params: SampleSelectQueryParams; - result: ISampleSelectQueryResult; -} - -export type SampleInsertQueryParams = [string]; - -export interface ISampleInsertQueryResult { - -} - -export interface ISampleInsertQueryQuery { - params: SampleInsertQueryParams; - result: ISampleInsertQueryResult; -} - -export type SampleUpdateQueryParams = [string, number]; - -export interface ISampleUpdateQueryResult { - -} - -export interface ISampleUpdateQueryQuery { - params: SampleUpdateQueryParams; - result: ISampleUpdateQueryResult; -} - -export type SampleDeleteQueryParams = [number]; - -export interface ISampleDeleteQueryResult { - -} - -export interface ISampleDeleteQueryQuery { - params: SampleDeleteQueryParams; - result: ISampleDeleteQueryResult; -} From 03e6d5f487cb948cb8a188397301b46c5527a7dd Mon Sep 17 00:00:00 2001 From: JasonShin Date: Wed, 4 Mar 2026 23:17:07 +1100 Subject: [PATCH 02/19] add tests for mysql --- .sqlxrc.sample.json | 3 +- playpen/db/mysql_migration.sql | 51 +++++ playpen/db/postgres_migration.sql | 51 +++++ .../mysql/json_access_operators.queries.ts | 128 +++++++++++ .../mysql/json_access_operators.snapshot.ts | 128 +++++++++++ tests/demo/mysql/json_access_operators.ts | 124 +++++++++++ .../mysql/json_array_functions.queries.ts | 130 +++++++++++ .../mysql/json_array_functions.snapshot.ts | 130 +++++++++++ tests/demo/mysql/json_array_functions.ts | 127 +++++++++++ .../demo/mysql/json_comprehensive.queries.ts | 175 +++++++++++++++ .../demo/mysql/json_comprehensive.snapshot.ts | 175 +++++++++++++++ tests/demo/mysql/json_comprehensive.ts | 171 ++++++++++++++ .../mysql/json_object_functions.queries.ts | 210 ++++++++++++++++++ .../mysql/json_object_functions.snapshot.ts | 210 ++++++++++++++++++ tests/demo/mysql/json_object_functions.ts | 206 +++++++++++++++++ tests/demo/mysql/json_operations.queries.ts | 36 +++ tests/demo/mysql/json_operations.snapshot.ts | 36 +++ tests/demo/mysql/json_operations.ts | 40 ++++ .../postgres/json_access_operators.queries.ts | 129 +++++++++++ .../json_access_operators.snapshot.ts | 129 +++++++++++ tests/demo/postgres/json_access_operators.ts | 116 ++++++++++ .../postgres/json_array_functions.queries.ts | 26 +++ .../postgres/json_array_functions.snapshot.ts | 26 +++ tests/demo/postgres/json_array_functions.ts | 25 +++ .../postgres/json_comprehensive.queries.ts | 136 ++++++++++++ .../postgres/json_comprehensive.snapshot.ts | 136 ++++++++++++ tests/demo/postgres/json_comprehensive.ts | 124 +++++++++++ .../postgres/json_object_functions.queries.ts | 142 ++++++++++++ .../json_object_functions.snapshot.ts | 143 ++++++++++++ tests/demo/postgres/json_object_functions.ts | 126 +++++++++++ 30 files changed, 3387 insertions(+), 2 deletions(-) create mode 100644 tests/demo/mysql/json_access_operators.queries.ts create mode 100644 tests/demo/mysql/json_access_operators.snapshot.ts create mode 100644 tests/demo/mysql/json_access_operators.ts create mode 100644 tests/demo/mysql/json_array_functions.queries.ts create mode 100644 tests/demo/mysql/json_array_functions.snapshot.ts create mode 100644 tests/demo/mysql/json_array_functions.ts create mode 100644 tests/demo/mysql/json_comprehensive.queries.ts create mode 100644 tests/demo/mysql/json_comprehensive.snapshot.ts create mode 100644 tests/demo/mysql/json_comprehensive.ts create mode 100644 tests/demo/mysql/json_object_functions.queries.ts create mode 100644 tests/demo/mysql/json_object_functions.snapshot.ts create mode 100644 tests/demo/mysql/json_object_functions.ts create mode 100644 tests/demo/mysql/json_operations.queries.ts create mode 100644 tests/demo/mysql/json_operations.snapshot.ts create mode 100644 tests/demo/mysql/json_operations.ts create mode 100644 tests/demo/postgres/json_access_operators.queries.ts create mode 100644 tests/demo/postgres/json_access_operators.snapshot.ts create mode 100644 tests/demo/postgres/json_access_operators.ts create mode 100644 tests/demo/postgres/json_array_functions.queries.ts create mode 100644 tests/demo/postgres/json_array_functions.snapshot.ts create mode 100644 tests/demo/postgres/json_array_functions.ts create mode 100644 tests/demo/postgres/json_comprehensive.queries.ts create mode 100644 tests/demo/postgres/json_comprehensive.snapshot.ts create mode 100644 tests/demo/postgres/json_comprehensive.ts create mode 100644 tests/demo/postgres/json_object_functions.queries.ts create mode 100644 tests/demo/postgres/json_object_functions.snapshot.ts create mode 100644 tests/demo/postgres/json_object_functions.ts diff --git a/.sqlxrc.sample.json b/.sqlxrc.sample.json index ee7909cf..a763b08e 100644 --- a/.sqlxrc.sample.json +++ b/.sqlxrc.sample.json @@ -11,8 +11,7 @@ "DB_PORT": 54321, "DB_USER": "postgres", "DB_PASS": "postgres", - "DB_NAME": "postgres", - "PG_SEARCH_PATH": "public,myschema" + "DB_NAME": "postgres" }, "db_mysql": { "DB_TYPE": "mysql", diff --git a/playpen/db/mysql_migration.sql b/playpen/db/mysql_migration.sql index 51f81fbb..ca03f912 100644 --- a/playpen/db/mysql_migration.sql +++ b/playpen/db/mysql_migration.sql @@ -172,3 +172,54 @@ CREATE TABLE random ( -- JSON types json1 JSON ); + +-- JSON Test Data Table +-- This table contains various JSON structures for testing JSON operators and functions +CREATE TABLE json_test_data ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + data JSON NOT NULL, + metadata JSON, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO json_test_data (name, data, metadata) VALUES +-- Simple object +('user_profile', + '{"userId": 1, "username": "john_doe", "email": "john@example.com", "age": 30, "active": true}', + '{"source": "api", "version": "1.0"}'), + +-- Nested object with address +('user_with_address', + '{"userId": 2, "username": "jane_smith", "email": "jane@example.com", "address": {"street": "123 Main St", "city": "Springfield", "state": "IL", "zipCode": "62701", "country": "USA"}}', + '{"source": "import", "version": "1.0"}'), + +-- Array of items +('shopping_cart', + '{"cartId": 101, "items": [{"productId": 1, "name": "Laptop", "quantity": 1, "price": 999.99}, {"productId": 2, "name": "Mouse", "quantity": 2, "price": 25.50}], "totalPrice": 1050.99}', + '{"source": "web", "version": "2.0"}'), + +-- Array of strings +('tags', + '{"postId": 42, "title": "MySQL JSON Functions", "tags": ["database", "mysql", "json", "tutorial"], "published": true}', + '{"source": "cms", "version": "1.0"}'), + +-- Nested arrays and objects +('game_stats', + '{"playerId": 123, "stats": {"level": 50, "experience": 125000, "inventory": [{"slot": 1, "item": "Sword of Fire", "rarity": "legendary"}, {"slot": 2, "item": "Shield of Light", "rarity": "epic"}], "achievements": ["First Kill", "Level 50", "Legendary Item"]}}', + '{"source": "game_server", "version": "3.0"}'), + +-- Deep nesting +('nested_config', + '{"app": {"name": "MyApp", "version": "1.0.0", "settings": {"database": {"host": "localhost", "port": 3306, "credentials": {"username": "admin", "encrypted": true}}, "features": {"darkMode": true, "notifications": {"email": true, "push": false}}}}}', + '{"source": "config", "version": "1.0"}'), + +-- Array of objects with nulls +('product_reviews', + '{"productId": 456, "reviews": [{"reviewId": 1, "rating": 5, "comment": "Excellent product!", "reviewer": "Alice"}, {"reviewId": 2, "rating": 4, "comment": null, "reviewer": "Bob"}, {"reviewId": 3, "rating": 3, "comment": "Average", "reviewer": null}]}', + '{"source": "reviews", "version": "1.0"}'), + +-- Mixed types +('analytics', + '{"date": "2024-01-15", "metrics": {"visitors": 1500, "pageViews": 4500, "bounceRate": 0.35, "sources": {"organic": 850, "direct": 400, "referral": 250}}}', + '{"source": "analytics", "version": "1.0"}'); diff --git a/playpen/db/postgres_migration.sql b/playpen/db/postgres_migration.sql index 7553bdfb..5caf844a 100644 --- a/playpen/db/postgres_migration.sql +++ b/playpen/db/postgres_migration.sql @@ -218,3 +218,54 @@ INSERT INTO classes (name, specialization) VALUES ('druid', '{"role": "hybrid", "weapon": "staff", "abilities": ["shapeshift", "moonfire", "regrowth"]}'), ('mage', '{"role": "ranged", "weapon": "wand", "abilities": ["fireball", "frostbolt", "arcane blast"]}'), ('warlock', '{"role": "ranged", "weapon": "dagger", "abilities": ["summon demon", "shadowbolt", "curse of agony"]}'); + +-- JSON Test Data Table +-- This table contains various JSON structures for testing JSON operators and functions +CREATE TABLE json_test_data ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + data JSONB NOT NULL, + metadata JSON, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO json_test_data (name, data, metadata) VALUES +-- Simple object +('user_profile', + '{"userId": 1, "username": "john_doe", "email": "john@example.com", "age": 30, "active": true}', + '{"source": "api", "version": "1.0"}'), + +-- Nested object with address +('user_with_address', + '{"userId": 2, "username": "jane_smith", "email": "jane@example.com", "address": {"street": "123 Main St", "city": "Springfield", "state": "IL", "zipCode": "62701", "country": "USA"}}', + '{"source": "import", "version": "1.0"}'), + +-- Array of items +('shopping_cart', + '{"cartId": 101, "items": [{"productId": 1, "name": "Laptop", "quantity": 1, "price": 999.99}, {"productId": 2, "name": "Mouse", "quantity": 2, "price": 25.50}], "totalPrice": 1050.99}', + '{"source": "web", "version": "2.0"}'), + +-- Array of strings +('tags', + '{"postId": 42, "title": "PostgreSQL JSON Functions", "tags": ["database", "postgresql", "json", "tutorial"], "published": true}', + '{"source": "cms", "version": "1.0"}'), + +-- Nested arrays and objects +('game_stats', + '{"playerId": 123, "stats": {"level": 50, "experience": 125000, "inventory": [{"slot": 1, "item": "Sword of Fire", "rarity": "legendary"}, {"slot": 2, "item": "Shield of Light", "rarity": "epic"}], "achievements": ["First Kill", "Level 50", "Legendary Item"]}}', + '{"source": "game_server", "version": "3.0"}'), + +-- Deep nesting +('nested_config', + '{"app": {"name": "MyApp", "version": "1.0.0", "settings": {"database": {"host": "localhost", "port": 5432, "credentials": {"username": "admin", "encrypted": true}}, "features": {"darkMode": true, "notifications": {"email": true, "push": false}}}}}', + '{"source": "config", "version": "1.0"}'), + +-- Array of objects with nulls +('product_reviews', + '{"productId": 456, "reviews": [{"reviewId": 1, "rating": 5, "comment": "Excellent product!", "reviewer": "Alice"}, {"reviewId": 2, "rating": 4, "comment": null, "reviewer": "Bob"}, {"reviewId": 3, "rating": 3, "comment": "Average", "reviewer": null}]}', + '{"source": "reviews", "version": "1.0"}'), + +-- Mixed types +('analytics', + '{"date": "2024-01-15", "metrics": {"visitors": 1500, "pageViews": 4500, "bounceRate": 0.35, "sources": {"organic": 850, "direct": 400, "referral": 250}}}', + '{"source": "analytics", "version": "1.0"}'); diff --git a/tests/demo/mysql/json_access_operators.queries.ts b/tests/demo/mysql/json_access_operators.queries.ts new file mode 100644 index 00000000..81a2595e --- /dev/null +++ b/tests/demo/mysql/json_access_operators.queries.ts @@ -0,0 +1,128 @@ +export type JsonFieldAccessParams = []; + +export interface IJsonFieldAccessResult { + activeJson: string; + ageJson: string; + id: number; + name: string; + usernameJson: string; +} + +export interface IJsonFieldAccessQuery { + params: JsonFieldAccessParams; + result: IJsonFieldAccessResult; +} + +export type JsonFieldAccessTextParams = []; + +export interface IJsonFieldAccessTextResult { + active: number; + age: number; + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFieldAccessTextQuery { + params: JsonFieldAccessTextParams; + result: IJsonFieldAccessTextResult; +} + +export type JsonNestedAccessParams = []; + +export interface IJsonNestedAccessResult { + addressJson: string; + city: any; + cityJson: string; + id: number; + name: string; + street: any; + zipCode: any; +} + +export interface IJsonNestedAccessQuery { + params: JsonNestedAccessParams; + result: IJsonNestedAccessResult; +} + +export type JsonArrayAccessParams = []; + +export interface IJsonArrayAccessResult { + firstItemJson: string; + firstItemName: any; + firstItemPrice: number; + id: number; + itemsJson: string; + name: string; + secondItemJson: string; +} + +export interface IJsonArrayAccessQuery { + params: JsonArrayAccessParams; + result: IJsonArrayAccessResult; +} + +export type JsonPathAccessParams = []; + +export interface IJsonPathAccessResult { + firstItemJson: string; + firstItemName: any; + firstItemRarity: any; + id: number; + level: number; + levelJson: string; + name: string; +} + +export interface IJsonPathAccessQuery { + params: JsonPathAccessParams; + result: IJsonPathAccessResult; +} + +export type JsonDeepPathAccessParams = []; + +export interface IJsonDeepPathAccessResult { + darkMode: number; + dbHost: any; + dbHostJson: string; + dbPort: number; + emailNotifications: number; + id: number; + name: string; +} + +export interface IJsonDeepPathAccessQuery { + params: JsonDeepPathAccessParams; + result: IJsonDeepPathAccessResult; +} + +export type JsonFilterByFieldParams = []; + +export interface IJsonFilterByFieldResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFilterByFieldQuery { + params: JsonFilterByFieldParams; + result: IJsonFilterByFieldResult; +} + +export type JsonNullHandlingParams = []; + +export interface IJsonNullHandlingResult { + firstComment: any; + firstReviewer: any; + id: number; + secondComment: any; + thirdComment: any; + thirdReviewer: any; +} + +export interface IJsonNullHandlingQuery { + params: JsonNullHandlingParams; + result: IJsonNullHandlingResult; +} diff --git a/tests/demo/mysql/json_access_operators.snapshot.ts b/tests/demo/mysql/json_access_operators.snapshot.ts new file mode 100644 index 00000000..81a2595e --- /dev/null +++ b/tests/demo/mysql/json_access_operators.snapshot.ts @@ -0,0 +1,128 @@ +export type JsonFieldAccessParams = []; + +export interface IJsonFieldAccessResult { + activeJson: string; + ageJson: string; + id: number; + name: string; + usernameJson: string; +} + +export interface IJsonFieldAccessQuery { + params: JsonFieldAccessParams; + result: IJsonFieldAccessResult; +} + +export type JsonFieldAccessTextParams = []; + +export interface IJsonFieldAccessTextResult { + active: number; + age: number; + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFieldAccessTextQuery { + params: JsonFieldAccessTextParams; + result: IJsonFieldAccessTextResult; +} + +export type JsonNestedAccessParams = []; + +export interface IJsonNestedAccessResult { + addressJson: string; + city: any; + cityJson: string; + id: number; + name: string; + street: any; + zipCode: any; +} + +export interface IJsonNestedAccessQuery { + params: JsonNestedAccessParams; + result: IJsonNestedAccessResult; +} + +export type JsonArrayAccessParams = []; + +export interface IJsonArrayAccessResult { + firstItemJson: string; + firstItemName: any; + firstItemPrice: number; + id: number; + itemsJson: string; + name: string; + secondItemJson: string; +} + +export interface IJsonArrayAccessQuery { + params: JsonArrayAccessParams; + result: IJsonArrayAccessResult; +} + +export type JsonPathAccessParams = []; + +export interface IJsonPathAccessResult { + firstItemJson: string; + firstItemName: any; + firstItemRarity: any; + id: number; + level: number; + levelJson: string; + name: string; +} + +export interface IJsonPathAccessQuery { + params: JsonPathAccessParams; + result: IJsonPathAccessResult; +} + +export type JsonDeepPathAccessParams = []; + +export interface IJsonDeepPathAccessResult { + darkMode: number; + dbHost: any; + dbHostJson: string; + dbPort: number; + emailNotifications: number; + id: number; + name: string; +} + +export interface IJsonDeepPathAccessQuery { + params: JsonDeepPathAccessParams; + result: IJsonDeepPathAccessResult; +} + +export type JsonFilterByFieldParams = []; + +export interface IJsonFilterByFieldResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFilterByFieldQuery { + params: JsonFilterByFieldParams; + result: IJsonFilterByFieldResult; +} + +export type JsonNullHandlingParams = []; + +export interface IJsonNullHandlingResult { + firstComment: any; + firstReviewer: any; + id: number; + secondComment: any; + thirdComment: any; + thirdReviewer: any; +} + +export interface IJsonNullHandlingQuery { + params: JsonNullHandlingParams; + result: IJsonNullHandlingResult; +} diff --git a/tests/demo/mysql/json_access_operators.ts b/tests/demo/mysql/json_access_operators.ts new file mode 100644 index 00000000..4722dc85 --- /dev/null +++ b/tests/demo/mysql/json_access_operators.ts @@ -0,0 +1,124 @@ +import { sql } from 'sqlx-ts' + + +// Test -> operator (get JSON field as JSON) +const jsonFieldAccess = sql` +-- @db: db_mysql +-- @name: json field access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data -> '$.username' AS username_json, + data -> '$.age' AS age_json, + data -> '$.active' AS active_json +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test ->> operator (get JSON field as text) +const jsonFieldAccessText = sql` +-- @db: db_mysql +-- @name: json field access text +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data ->> '$.username' AS username, + data ->> '$.email' AS email, + CAST(data ->> '$.age' AS UNSIGNED) AS age, + CAST(data ->> '$.active' AS UNSIGNED) AS active +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test nested field access with JSON_EXTRACT +const jsonNestedAccess = sql` +-- @db: db_mysql +-- @name: json nested access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data -> '$.address' AS address_json, + data -> '$.address.city' AS city_json, + JSON_UNQUOTE(data -> '$.address.city') AS city, + JSON_UNQUOTE(data -> '$.address.zipCode') AS zip_code, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.street')) AS street +FROM json_test_data +WHERE json_test_data.name = 'user_with_address' +` + +// Test array element access by index +const jsonArrayAccess = sql` +-- @db: db_mysql +-- @name: json array access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data -> '$.items' AS items_json, + data -> '$.items[0]' AS first_item_json, + data -> '$.items[1]' AS second_item_json, + JSON_UNQUOTE(data -> '$.items[0].name') AS first_item_name, + CAST(data -> '$.items[0].price' AS DECIMAL(10,2)) AS first_item_price +FROM json_test_data +WHERE json_test_data.name = 'shopping_cart' +` + +// Test path access with deep nesting +const jsonPathAccess = sql` +-- @db: db_mysql +-- @name: json path access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data -> '$.stats.level' AS level_json, + data -> '$.stats.inventory[0].item' AS first_item_json, + CAST(JSON_UNQUOTE(data -> '$.stats.level') AS UNSIGNED) AS level, + JSON_UNQUOTE(data -> '$.stats.inventory[0].item') AS first_item_name, + JSON_UNQUOTE(data -> '$.stats.inventory[0].rarity') AS first_item_rarity +FROM json_test_data +WHERE json_test_data.name = 'game_stats' +` + +// Test deep nested path access +const jsonDeepPathAccess = sql` +-- @db: db_mysql +-- @name: json deep path access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data -> '$.app.settings.database.host' AS db_host_json, + JSON_UNQUOTE(data -> '$.app.settings.database.host') AS db_host, + CAST(JSON_UNQUOTE(data -> '$.app.settings.database.port') AS UNSIGNED) AS db_port, + CAST(JSON_UNQUOTE(data -> '$.app.settings.features.darkMode') AS UNSIGNED) AS dark_mode, + CAST(JSON_UNQUOTE(data -> '$.app.settings.features.notifications.email') AS UNSIGNED) AS email_notifications +FROM json_test_data +WHERE json_test_data.name = 'nested_config' +` + +// Test filtering by JSON field +const jsonFilterByField = sql` +-- @db: db_mysql +-- @name: json filter by field +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data ->> '$.username' AS username, + data ->> '$.email' AS email +FROM json_test_data +WHERE data ->> '$.active' = 'true' + AND CAST(data ->> '$.age' AS UNSIGNED) > 25 +` + +// Test null handling in JSON +const jsonNullHandling = sql` +-- @db: db_mysql +-- @name: json null handling +SELECT + json_test_data.id AS id, + JSON_UNQUOTE(data -> '$.reviews[0].comment') AS first_comment, + JSON_UNQUOTE(data -> '$.reviews[1].comment') AS second_comment, + JSON_UNQUOTE(data -> '$.reviews[2].comment') AS third_comment, + JSON_UNQUOTE(data -> '$.reviews[0].reviewer') AS first_reviewer, + JSON_UNQUOTE(data -> '$.reviews[2].reviewer') AS third_reviewer +FROM json_test_data +WHERE json_test_data.name = 'product_reviews' +` diff --git a/tests/demo/mysql/json_array_functions.queries.ts b/tests/demo/mysql/json_array_functions.queries.ts new file mode 100644 index 00000000..6b057d4f --- /dev/null +++ b/tests/demo/mysql/json_array_functions.queries.ts @@ -0,0 +1,130 @@ +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + itemsCount: any; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonArrayExtractParams = []; + +export interface IJsonArrayExtractResult { + firstTag: any; + id: number; + name: string; + secondTag: any; + thirdTag: any; +} + +export interface IJsonArrayExtractQuery { + params: JsonArrayExtractParams; + result: IJsonArrayExtractResult; +} + +export type JsonArrayContainsParams = []; + +export interface IJsonArrayContainsResult { + hasDatabase: any; + hasMysql: any; + id: number; + name: string; + tags: any; +} + +export interface IJsonArrayContainsQuery { + params: JsonArrayContainsParams; + result: IJsonArrayContainsResult; +} + +export type JsonArrayMembershipParams = []; + +export interface IJsonArrayMembershipResult { + hasMysqlTag: any; + hasTutorialTag: any; + id: number; + name: string; +} + +export interface IJsonArrayMembershipQuery { + params: JsonArrayMembershipParams; + result: IJsonArrayMembershipResult; +} + +export type JsonNestedArrayAccessParams = []; + +export interface IJsonNestedArrayAccessResult { + firstItemName: any; + firstItemPrice: any; + id: number; + name: string; + secondItemName: any; + secondItemQuantity: any; +} + +export interface IJsonNestedArrayAccessQuery { + params: JsonNestedArrayAccessParams; + result: IJsonNestedArrayAccessResult; +} + +export type JsonDeepNestedArrayParams = []; + +export interface IJsonDeepNestedArrayResult { + firstAchievement: any; + firstInventoryItem: any; + firstItemRarity: any; + id: number; + name: string; + secondInventoryItem: any; +} + +export interface IJsonDeepNestedArrayQuery { + params: JsonDeepNestedArrayParams; + result: IJsonDeepNestedArrayResult; +} + +export type JsonArrayBuildParams = []; + +export interface IJsonArrayBuildResult { + firstTwoTags: any; + id: number; + name: string; +} + +export interface IJsonArrayBuildQuery { + params: JsonArrayBuildParams; + result: IJsonArrayBuildResult; +} + +export type JsonArrayAppendParams = []; + +export interface IJsonArrayAppendResult { + id: number; + name: string; + originalTags: any; + tagsWithNew: any; +} + +export interface IJsonArrayAppendQuery { + params: JsonArrayAppendParams; + result: IJsonArrayAppendResult; +} + +export type JsonArrayInsertParams = []; + +export interface IJsonArrayInsertResult { + id: number; + name: string; + originalTags: any; + tagsWithInsert: any; +} + +export interface IJsonArrayInsertQuery { + params: JsonArrayInsertParams; + result: IJsonArrayInsertResult; +} diff --git a/tests/demo/mysql/json_array_functions.snapshot.ts b/tests/demo/mysql/json_array_functions.snapshot.ts new file mode 100644 index 00000000..6b057d4f --- /dev/null +++ b/tests/demo/mysql/json_array_functions.snapshot.ts @@ -0,0 +1,130 @@ +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + itemsCount: any; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonArrayExtractParams = []; + +export interface IJsonArrayExtractResult { + firstTag: any; + id: number; + name: string; + secondTag: any; + thirdTag: any; +} + +export interface IJsonArrayExtractQuery { + params: JsonArrayExtractParams; + result: IJsonArrayExtractResult; +} + +export type JsonArrayContainsParams = []; + +export interface IJsonArrayContainsResult { + hasDatabase: any; + hasMysql: any; + id: number; + name: string; + tags: any; +} + +export interface IJsonArrayContainsQuery { + params: JsonArrayContainsParams; + result: IJsonArrayContainsResult; +} + +export type JsonArrayMembershipParams = []; + +export interface IJsonArrayMembershipResult { + hasMysqlTag: any; + hasTutorialTag: any; + id: number; + name: string; +} + +export interface IJsonArrayMembershipQuery { + params: JsonArrayMembershipParams; + result: IJsonArrayMembershipResult; +} + +export type JsonNestedArrayAccessParams = []; + +export interface IJsonNestedArrayAccessResult { + firstItemName: any; + firstItemPrice: any; + id: number; + name: string; + secondItemName: any; + secondItemQuantity: any; +} + +export interface IJsonNestedArrayAccessQuery { + params: JsonNestedArrayAccessParams; + result: IJsonNestedArrayAccessResult; +} + +export type JsonDeepNestedArrayParams = []; + +export interface IJsonDeepNestedArrayResult { + firstAchievement: any; + firstInventoryItem: any; + firstItemRarity: any; + id: number; + name: string; + secondInventoryItem: any; +} + +export interface IJsonDeepNestedArrayQuery { + params: JsonDeepNestedArrayParams; + result: IJsonDeepNestedArrayResult; +} + +export type JsonArrayBuildParams = []; + +export interface IJsonArrayBuildResult { + firstTwoTags: any; + id: number; + name: string; +} + +export interface IJsonArrayBuildQuery { + params: JsonArrayBuildParams; + result: IJsonArrayBuildResult; +} + +export type JsonArrayAppendParams = []; + +export interface IJsonArrayAppendResult { + id: number; + name: string; + originalTags: any; + tagsWithNew: any; +} + +export interface IJsonArrayAppendQuery { + params: JsonArrayAppendParams; + result: IJsonArrayAppendResult; +} + +export type JsonArrayInsertParams = []; + +export interface IJsonArrayInsertResult { + id: number; + name: string; + originalTags: any; + tagsWithInsert: any; +} + +export interface IJsonArrayInsertQuery { + params: JsonArrayInsertParams; + result: IJsonArrayInsertResult; +} diff --git a/tests/demo/mysql/json_array_functions.ts b/tests/demo/mysql/json_array_functions.ts new file mode 100644 index 00000000..bc5ddeb2 --- /dev/null +++ b/tests/demo/mysql/json_array_functions.ts @@ -0,0 +1,127 @@ +import { sql } from 'sqlx-ts' + + +// Test JSON_LENGTH - get array length +const jsonArrayLength = sql` +-- @db: db_mysql +-- @name: json array length +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_LENGTH(data, '$.items') AS items_count, + JSON_LENGTH(data, '$.tags') AS tags_count +FROM json_test_data +WHERE json_test_data.name IN ('shopping_cart', 'tags') +` + +// Test JSON_EXTRACT with array index +const jsonArrayExtract = sql` +-- @db: db_mysql +-- @name: json array extract +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.tags[0]') AS first_tag, + JSON_EXTRACT(data, '$.tags[1]') AS second_tag, + JSON_EXTRACT(data, '$.tags[2]') AS third_tag +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test array contains using JSON_CONTAINS +const jsonArrayContains = sql` +-- @db: db_mysql +-- @name: json array contains +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.tags') AS tags, + JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('mysql')) AS has_mysql, + JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('database')) AS has_database +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test array element membership +const jsonArrayMembership = sql` +-- @db: db_mysql +-- @name: json array membership +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('mysql')) AS has_mysql_tag, + JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('tutorial')) AS has_tutorial_tag +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test nested array access +const jsonNestedArrayAccess = sql` +-- @db: db_mysql +-- @name: json nested array access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.items[0].name') AS first_item_name, + JSON_EXTRACT(data, '$.items[0].price') AS first_item_price, + JSON_EXTRACT(data, '$.items[1].name') AS second_item_name, + JSON_EXTRACT(data, '$.items[1].quantity') AS second_item_quantity +FROM json_test_data +WHERE json_test_data.name = 'shopping_cart' +` + +// Test deep nested array +const jsonDeepNestedArray = sql` +-- @db: db_mysql +-- @name: json deep nested array +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.stats.inventory[0].item') AS first_inventory_item, + JSON_EXTRACT(data, '$.stats.inventory[0].rarity') AS first_item_rarity, + JSON_EXTRACT(data, '$.stats.inventory[1].item') AS second_inventory_item, + JSON_EXTRACT(data, '$.stats.achievements[0]') AS first_achievement +FROM json_test_data +WHERE json_test_data.name = 'game_stats' +` + +// Test JSON_ARRAY to build arrays +const jsonArrayBuild = sql` +-- @db: db_mysql +-- @name: json array build +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_ARRAY( + JSON_EXTRACT(data, '$.tags[0]'), + JSON_EXTRACT(data, '$.tags[1]') + ) AS first_two_tags +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test JSON_ARRAY_APPEND to add elements +const jsonArrayAppend = sql` +-- @db: db_mysql +-- @name: json array append +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.tags') AS original_tags, + JSON_ARRAY_APPEND(JSON_EXTRACT(data, '$.tags'), '$', 'new_tag') AS tags_with_new +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test JSON_ARRAY_INSERT to insert elements +const jsonArrayInsert = sql` +-- @db: db_mysql +-- @name: json array insert +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.tags') AS original_tags, + JSON_ARRAY_INSERT(JSON_EXTRACT(data, '$.tags'), '$[1]', 'inserted_tag') AS tags_with_insert +FROM json_test_data +WHERE json_test_data.name = 'tags' +` diff --git a/tests/demo/mysql/json_comprehensive.queries.ts b/tests/demo/mysql/json_comprehensive.queries.ts new file mode 100644 index 00000000..1067842b --- /dev/null +++ b/tests/demo/mysql/json_comprehensive.queries.ts @@ -0,0 +1,175 @@ +export type JsonExtractParams = []; + +export interface IJsonExtractResult { + age: number; + email: any; + id: number; + name: string; + username: any; +} + +export interface IJsonExtractQuery { + params: JsonExtractParams; + result: IJsonExtractResult; +} + +export type JsonExtractShorthandParams = []; + +export interface IJsonExtractShorthandResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonExtractShorthandQuery { + params: JsonExtractShorthandParams; + result: IJsonExtractShorthandResult; +} + +export type JsonNestedPathParams = []; + +export interface IJsonNestedPathResult { + city: any; + id: number; + name: string; + zipCode: any; +} + +export interface IJsonNestedPathQuery { + params: JsonNestedPathParams; + result: IJsonNestedPathResult; +} + +export type JsonArrayIndexParams = []; + +export interface IJsonArrayIndexResult { + firstItemName: any; + firstItemPrice: number; + id: number; + name: string; +} + +export interface IJsonArrayIndexQuery { + params: JsonArrayIndexParams; + result: IJsonArrayIndexResult; +} + +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonTypeParams = []; + +export interface IJsonTypeResult { + ageType: any; + id: number; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeQuery { + params: JsonTypeParams; + result: IJsonTypeResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + id: number; + isActive: any; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonKeysParams = []; + +export interface IJsonKeysResult { + allKeys: any; + id: number; + name: string; +} + +export interface IJsonKeysQuery { + params: JsonKeysParams; + result: IJsonKeysResult; +} + +export type JsonObjectBuildParams = []; + +export interface IJsonObjectBuildResult { + id: number; + name: string; + userSummary: any; +} + +export interface IJsonObjectBuildQuery { + params: JsonObjectBuildParams; + result: IJsonObjectBuildResult; +} + +export type JsonFilterParams = []; + +export interface IJsonFilterResult { + id: number; + name: string; + username: any; +} + +export interface IJsonFilterQuery { + params: JsonFilterParams; + result: IJsonFilterResult; +} + +export type JsonDeepPathParams = []; + +export interface IJsonDeepPathResult { + appName: any; + dbHost: any; + dbPort: number; + id: number; +} + +export interface IJsonDeepPathQuery { + params: JsonDeepPathParams; + result: IJsonDeepPathResult; +} + +export type JsonValidParams = []; + +export interface IJsonValidResult { + id: number; + isValidJson: any; + name: string; +} + +export interface IJsonValidQuery { + params: JsonValidParams; + result: IJsonValidResult; +} + +export type JsonSearchParams = []; + +export interface IJsonSearchResult { + id: number; + name: string; + usernamePath: any; +} + +export interface IJsonSearchQuery { + params: JsonSearchParams; + result: IJsonSearchResult; +} diff --git a/tests/demo/mysql/json_comprehensive.snapshot.ts b/tests/demo/mysql/json_comprehensive.snapshot.ts new file mode 100644 index 00000000..1067842b --- /dev/null +++ b/tests/demo/mysql/json_comprehensive.snapshot.ts @@ -0,0 +1,175 @@ +export type JsonExtractParams = []; + +export interface IJsonExtractResult { + age: number; + email: any; + id: number; + name: string; + username: any; +} + +export interface IJsonExtractQuery { + params: JsonExtractParams; + result: IJsonExtractResult; +} + +export type JsonExtractShorthandParams = []; + +export interface IJsonExtractShorthandResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonExtractShorthandQuery { + params: JsonExtractShorthandParams; + result: IJsonExtractShorthandResult; +} + +export type JsonNestedPathParams = []; + +export interface IJsonNestedPathResult { + city: any; + id: number; + name: string; + zipCode: any; +} + +export interface IJsonNestedPathQuery { + params: JsonNestedPathParams; + result: IJsonNestedPathResult; +} + +export type JsonArrayIndexParams = []; + +export interface IJsonArrayIndexResult { + firstItemName: any; + firstItemPrice: number; + id: number; + name: string; +} + +export interface IJsonArrayIndexQuery { + params: JsonArrayIndexParams; + result: IJsonArrayIndexResult; +} + +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonTypeParams = []; + +export interface IJsonTypeResult { + ageType: any; + id: number; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeQuery { + params: JsonTypeParams; + result: IJsonTypeResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + id: number; + isActive: any; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonKeysParams = []; + +export interface IJsonKeysResult { + allKeys: any; + id: number; + name: string; +} + +export interface IJsonKeysQuery { + params: JsonKeysParams; + result: IJsonKeysResult; +} + +export type JsonObjectBuildParams = []; + +export interface IJsonObjectBuildResult { + id: number; + name: string; + userSummary: any; +} + +export interface IJsonObjectBuildQuery { + params: JsonObjectBuildParams; + result: IJsonObjectBuildResult; +} + +export type JsonFilterParams = []; + +export interface IJsonFilterResult { + id: number; + name: string; + username: any; +} + +export interface IJsonFilterQuery { + params: JsonFilterParams; + result: IJsonFilterResult; +} + +export type JsonDeepPathParams = []; + +export interface IJsonDeepPathResult { + appName: any; + dbHost: any; + dbPort: number; + id: number; +} + +export interface IJsonDeepPathQuery { + params: JsonDeepPathParams; + result: IJsonDeepPathResult; +} + +export type JsonValidParams = []; + +export interface IJsonValidResult { + id: number; + isValidJson: any; + name: string; +} + +export interface IJsonValidQuery { + params: JsonValidParams; + result: IJsonValidResult; +} + +export type JsonSearchParams = []; + +export interface IJsonSearchResult { + id: number; + name: string; + usernamePath: any; +} + +export interface IJsonSearchQuery { + params: JsonSearchParams; + result: IJsonSearchResult; +} diff --git a/tests/demo/mysql/json_comprehensive.ts b/tests/demo/mysql/json_comprehensive.ts new file mode 100644 index 00000000..86069e37 --- /dev/null +++ b/tests/demo/mysql/json_comprehensive.ts @@ -0,0 +1,171 @@ +import { sql } from 'sqlx-ts' + + +// Test 1: JSON_EXTRACT with -> operator +const jsonExtract = sql` +-- @db: db_mysql +-- @name: json extract +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.username')) AS username, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.email')) AS email, + CAST(JSON_EXTRACT(data, '$.age') AS UNSIGNED) AS age +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 2: JSON_EXTRACT with ->> operator (shorthand) +const jsonExtractShorthand = sql` +-- @db: db_mysql +-- @name: json extract shorthand +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data->>'$.username' AS username, + data->>'$.email' AS email +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 3: Nested JSON path +const jsonNestedPath = sql` +-- @db: db_mysql +-- @name: json nested path +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.city')) AS city, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.zipCode')) AS zip_code +FROM json_test_data +WHERE json_test_data.name = 'user_with_address' +` + +// Test 4: JSON array index access +const jsonArrayIndex = sql` +-- @db: db_mysql +-- @name: json array index +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.items[0].name')) AS first_item_name, + CAST(JSON_EXTRACT(data, '$.items[0].price') AS DECIMAL(10,2)) AS first_item_price +FROM json_test_data +WHERE json_test_data.name = 'shopping_cart' +` + +// Test 5: JSON_LENGTH for array length +const jsonArrayLength = sql` +-- @db: db_mysql +-- @name: json array length +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_LENGTH(JSON_EXTRACT(data, '$.tags')) AS tags_count +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test 6: JSON_TYPE +const jsonType = sql` +-- @db: db_mysql +-- @name: json type +SELECT + json_test_data.id AS id, + JSON_TYPE(JSON_EXTRACT(data, '$.username')) AS username_type, + JSON_TYPE(JSON_EXTRACT(data, '$.age')) AS age_type, + JSON_TYPE(JSON_EXTRACT(data, '$.tags')) AS tags_type +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'tags') +` + +// Test 7: JSON_CONTAINS for containment check +const jsonContains = sql` +-- @db: db_mysql +-- @name: json contains +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_CONTAINS(data, 'true', '$.active') AS is_active +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 8: JSON_KEYS to get object keys +const jsonKeys = sql` +-- @db: db_mysql +-- @name: json keys +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_KEYS(data) AS all_keys +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 9: JSON_OBJECT to build objects with type inference +const jsonObjectBuild = sql` +-- @db: db_mysql +-- @name: json object build +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_OBJECT( + 'id', json_test_data.id, + 'name', json_test_data.name, + 'username', JSON_EXTRACT(data, '$.username'), + 'email', JSON_EXTRACT(data, '$.email') + ) AS user_summary +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 10: Filter using JSON values +const jsonFilter = sql` +-- @db: db_mysql +-- @name: json filter +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.username')) AS username +FROM json_test_data +WHERE CAST(JSON_EXTRACT(data, '$.active') AS UNSIGNED) = 1 + AND CAST(JSON_EXTRACT(data, '$.age') AS UNSIGNED) > 25 +` + +// Test 11: Deep nested path +const jsonDeepPath = sql` +-- @db: db_mysql +-- @name: json deep path +SELECT + json_test_data.id AS id, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.app.name')) AS app_name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.app.settings.database.host')) AS db_host, + CAST(JSON_EXTRACT(data, '$.app.settings.database.port') AS UNSIGNED) AS db_port +FROM json_test_data +WHERE json_test_data.name = 'nested_config' +` + +// Test 12: JSON_VALID +const jsonValid = sql` +-- @db: db_mysql +-- @name: json valid +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_VALID(data) AS is_valid_json +FROM json_test_data +LIMIT 3 +` + +// Test 13: JSON_SEARCH to find values +const jsonSearch = sql` +-- @db: db_mysql +-- @name: json search +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_SEARCH(data, 'one', 'john_doe') AS username_path +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` diff --git a/tests/demo/mysql/json_object_functions.queries.ts b/tests/demo/mysql/json_object_functions.queries.ts new file mode 100644 index 00000000..37a6c0f4 --- /dev/null +++ b/tests/demo/mysql/json_object_functions.queries.ts @@ -0,0 +1,210 @@ +export type JsonObjectKeysParams = []; + +export interface IJsonObjectKeysResult { + id: number; + name: string; + objectKeys: any; +} + +export interface IJsonObjectKeysQuery { + params: JsonObjectKeysParams; + result: IJsonObjectKeysResult; +} + +export type JsonObjectKeysPathParams = []; + +export interface IJsonObjectKeysPathResult { + addressKeys: any; + id: number; + name: string; +} + +export interface IJsonObjectKeysPathQuery { + params: JsonObjectKeysPathParams; + result: IJsonObjectKeysPathResult; +} + +export type JsonTypeofParams = []; + +export interface IJsonTypeofResult { + activeType: any; + ageType: any; + id: number; + itemsType: any; + name: string; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeofQuery { + params: JsonTypeofParams; + result: IJsonTypeofResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + hasSpecificUsername: any; + id: number; + isActive: any; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonContainsPathParams = []; + +export interface IJsonContainsPathResult { + hasAddress: any; + hasBoth: any; + hasNonexistent: any; + hasUsername: any; + id: number; + name: string; +} + +export interface IJsonContainsPathQuery { + params: JsonContainsPathParams; + result: IJsonContainsPathResult; +} + +export type JsonObjectBuildParams = []; + +export interface IJsonObjectBuildResult { + id: number; + name: string; + userSummary: any; +} + +export interface IJsonObjectBuildQuery { + params: JsonObjectBuildParams; + result: IJsonObjectBuildResult; +} + +export type JsonSetParams = []; + +export interface IJsonSetResult { + id: number; + name: string; + originalData: object; + updatedAge: any; + updatedCity: any; +} + +export interface IJsonSetQuery { + params: JsonSetParams; + result: IJsonSetResult; +} + +export type JsonInsertParams = []; + +export interface IJsonInsertResult { + id: number; + name: string; + originalData: object; + withPhone: any; +} + +export interface IJsonInsertQuery { + params: JsonInsertParams; + result: IJsonInsertResult; +} + +export type JsonReplaceParams = []; + +export interface IJsonReplaceResult { + id: number; + name: string; + originalData: object; + withNewUsername: any; +} + +export interface IJsonReplaceQuery { + params: JsonReplaceParams; + result: IJsonReplaceResult; +} + +export type JsonRemoveParams = []; + +export interface IJsonRemoveResult { + id: number; + name: string; + originalData: object; + withoutAge: any; +} + +export interface IJsonRemoveQuery { + params: JsonRemoveParams; + result: IJsonRemoveResult; +} + +export type JsonMergePatchParams = []; + +export interface IJsonMergePatchResult { + id: number; + mergedData: any; + name: string; + originalData: object; +} + +export interface IJsonMergePatchQuery { + params: JsonMergePatchParams; + result: IJsonMergePatchResult; +} + +export type JsonMergePreserveParams = []; + +export interface IJsonMergePreserveResult { + id: number; + mergedData: any; + name: string; + originalData: object; +} + +export interface IJsonMergePreserveQuery { + params: JsonMergePreserveParams; + result: IJsonMergePreserveResult; +} + +export type JsonSearchParams = []; + +export interface IJsonSearchResult { + emailPath: any; + id: number; + name: string; + usernamePath: any; +} + +export interface IJsonSearchQuery { + params: JsonSearchParams; + result: IJsonSearchResult; +} + +export type JsonDepthParams = []; + +export interface IJsonDepthResult { + dataDepth: any; + id: number; + name: string; +} + +export interface IJsonDepthQuery { + params: JsonDepthParams; + result: IJsonDepthResult; +} + +export type JsonValidParams = []; + +export interface IJsonValidResult { + id: number; + isValidJson: any; + name: string; +} + +export interface IJsonValidQuery { + params: JsonValidParams; + result: IJsonValidResult; +} diff --git a/tests/demo/mysql/json_object_functions.snapshot.ts b/tests/demo/mysql/json_object_functions.snapshot.ts new file mode 100644 index 00000000..dadda716 --- /dev/null +++ b/tests/demo/mysql/json_object_functions.snapshot.ts @@ -0,0 +1,210 @@ +export type JsonObjectKeysParams = []; + +export interface IJsonObjectKeysResult { + id: number; + name: string; + objectKeys: any; +} + +export interface IJsonObjectKeysQuery { + params: JsonObjectKeysParams; + result: IJsonObjectKeysResult; +} + +export type JsonObjectKeysPathParams = []; + +export interface IJsonObjectKeysPathResult { + addressKeys: any; + id: number; + name: string; +} + +export interface IJsonObjectKeysPathQuery { + params: JsonObjectKeysPathParams; + result: IJsonObjectKeysPathResult; +} + +export type JsonTypeofParams = []; + +export interface IJsonTypeofResult { + activeType: any; + ageType: any; + id: number; + itemsType: any; + name: string; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeofQuery { + params: JsonTypeofParams; + result: IJsonTypeofResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + hasSpecificUsername: any; + id: number; + isActive: any; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonContainsPathParams = []; + +export interface IJsonContainsPathResult { + hasAddress: any; + hasBoth: any; + hasNonexistent: any; + hasUsername: any; + id: number; + name: string; +} + +export interface IJsonContainsPathQuery { + params: JsonContainsPathParams; + result: IJsonContainsPathResult; +} + +export type JsonObjectBuildParams = []; + +export interface IJsonObjectBuildResult { + id: number; + name: string; + userSummary: any; +} + +export interface IJsonObjectBuildQuery { + params: JsonObjectBuildParams; + result: IJsonObjectBuildResult; +} + +export type JsonSetParams = []; + +export interface IJsonSetResult { + id: number; + name: string; + originalData: any; + updatedAge: any; + updatedCity: any; +} + +export interface IJsonSetQuery { + params: JsonSetParams; + result: IJsonSetResult; +} + +export type JsonInsertParams = []; + +export interface IJsonInsertResult { + id: number; + name: string; + originalData: any; + withPhone: any; +} + +export interface IJsonInsertQuery { + params: JsonInsertParams; + result: IJsonInsertResult; +} + +export type JsonReplaceParams = []; + +export interface IJsonReplaceResult { + id: number; + name: string; + originalData: any; + withNewUsername: any; +} + +export interface IJsonReplaceQuery { + params: JsonReplaceParams; + result: IJsonReplaceResult; +} + +export type JsonRemoveParams = []; + +export interface IJsonRemoveResult { + id: number; + name: string; + originalData: any; + withoutAge: any; +} + +export interface IJsonRemoveQuery { + params: JsonRemoveParams; + result: IJsonRemoveResult; +} + +export type JsonMergePatchParams = []; + +export interface IJsonMergePatchResult { + id: number; + mergedData: any; + name: string; + originalData: any; +} + +export interface IJsonMergePatchQuery { + params: JsonMergePatchParams; + result: IJsonMergePatchResult; +} + +export type JsonMergePreserveParams = []; + +export interface IJsonMergePreserveResult { + id: number; + mergedData: any; + name: string; + originalData: any; +} + +export interface IJsonMergePreserveQuery { + params: JsonMergePreserveParams; + result: IJsonMergePreserveResult; +} + +export type JsonSearchParams = []; + +export interface IJsonSearchResult { + emailPath: any; + id: number; + name: string; + usernamePath: any; +} + +export interface IJsonSearchQuery { + params: JsonSearchParams; + result: IJsonSearchResult; +} + +export type JsonDepthParams = []; + +export interface IJsonDepthResult { + dataDepth: any; + id: number; + name: string; +} + +export interface IJsonDepthQuery { + params: JsonDepthParams; + result: IJsonDepthResult; +} + +export type JsonValidParams = []; + +export interface IJsonValidResult { + id: number; + isValidJson: any; + name: string; +} + +export interface IJsonValidQuery { + params: JsonValidParams; + result: IJsonValidResult; +} diff --git a/tests/demo/mysql/json_object_functions.ts b/tests/demo/mysql/json_object_functions.ts new file mode 100644 index 00000000..e1c9b920 --- /dev/null +++ b/tests/demo/mysql/json_object_functions.ts @@ -0,0 +1,206 @@ +import { sql } from 'sqlx-ts' + + +// Test JSON_KEYS - get all keys from object +const jsonObjectKeys = sql` +-- @db: db_mysql +-- @name: json object keys +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_KEYS(data) AS object_keys +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_KEYS with path +const jsonObjectKeysPath = sql` +-- @db: db_mysql +-- @name: json object keys path +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_KEYS(data, '$.address') AS address_keys +FROM json_test_data +WHERE json_test_data.name = 'user_with_address' +` + +// Test JSON_TYPE - get type of JSON value +const jsonTypeof = sql` +-- @db: db_mysql +-- @name: json typeof +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_TYPE(JSON_EXTRACT(data, '$.username')) AS username_type, + JSON_TYPE(JSON_EXTRACT(data, '$.age')) AS age_type, + JSON_TYPE(JSON_EXTRACT(data, '$.active')) AS active_type, + JSON_TYPE(JSON_EXTRACT(data, '$.items')) AS items_type, + JSON_TYPE(JSON_EXTRACT(data, '$.tags')) AS tags_type +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'shopping_cart', 'tags') +` + +// Test JSON_CONTAINS - check if JSON contains value +const jsonContains = sql` +-- @db: db_mysql +-- @name: json contains +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_CONTAINS(data, JSON_QUOTE('john_doe'), '$.username') AS has_specific_username, + JSON_CONTAINS(data, 'true', '$.active') AS is_active +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_CONTAINS_PATH - check if path exists +const jsonContainsPath = sql` +-- @db: db_mysql +-- @name: json contains path +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_CONTAINS_PATH(data, 'one', '$.username') AS has_username, + JSON_CONTAINS_PATH(data, 'one', '$.address') AS has_address, + JSON_CONTAINS_PATH(data, 'one', '$.nonexistent') AS has_nonexistent, + JSON_CONTAINS_PATH(data, 'all', '$.username', '$.email') AS has_both +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +` + +// Test JSON_OBJECT - build JSON objects +const jsonObjectBuild = sql` +-- @db: db_mysql +-- @name: json object build +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_OBJECT( + 'id', json_test_data.id, + 'name', json_test_data.name, + 'username', JSON_EXTRACT(data, '$.username'), + 'email', JSON_EXTRACT(data, '$.email') + ) AS user_summary +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_SET - update value in JSON +const jsonSet = sql` +-- @db: db_mysql +-- @name: json set +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_SET(data, '$.age', 31) AS updated_age, + JSON_SET(data, '$.address.city', 'New York') AS updated_city +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +LIMIT 2 +` + +// Test JSON_INSERT - insert value into JSON +const jsonInsert = sql` +-- @db: db_mysql +-- @name: json insert +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_INSERT(data, '$.phone', '555-1234') AS with_phone +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_REPLACE - replace existing value +const jsonReplace = sql` +-- @db: db_mysql +-- @name: json replace +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_REPLACE(data, '$.username', 'new_username') AS with_new_username +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_REMOVE - remove keys from JSON +const jsonRemove = sql` +-- @db: db_mysql +-- @name: json remove +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_REMOVE(data, '$.age') AS without_age +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_MERGE_PATCH - merge JSON objects +const jsonMergePatch = sql` +-- @db: db_mysql +-- @name: json merge patch +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_MERGE_PATCH(data, JSON_OBJECT('verified', true, 'lastLogin', '2024-01-15')) AS merged_data +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_MERGE_PRESERVE - merge preserving all values +const jsonMergePreserve = sql` +-- @db: db_mysql +-- @name: json merge preserve +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_MERGE_PRESERVE(data, JSON_OBJECT('newField', 'newValue')) AS merged_data +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_SEARCH - find values in JSON +const jsonSearch = sql` +-- @db: db_mysql +-- @name: json search +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_SEARCH(data, 'one', 'john_doe') AS username_path, + JSON_SEARCH(data, 'one', 'john@example.com') AS email_path +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_DEPTH - get depth of JSON +const jsonDepth = sql` +-- @db: db_mysql +-- @db: db_mysql +-- @name: json depth +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_DEPTH(data) AS data_depth +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'nested_config') +` + +// Test JSON_VALID - validate JSON +const jsonValid = sql` +-- @db: db_mysql +-- @db: db_mysql +-- @name: json valid +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_VALID(data) AS is_valid_json +FROM json_test_data +LIMIT 3 +` diff --git a/tests/demo/mysql/json_operations.queries.ts b/tests/demo/mysql/json_operations.queries.ts new file mode 100644 index 00000000..ec3e5c7d --- /dev/null +++ b/tests/demo/mysql/json_operations.queries.ts @@ -0,0 +1,36 @@ +export type JsonObjectBasicParams = []; + +export interface IJsonObjectBasicResult { + id: number; + itemJson: any; +} + +export interface IJsonObjectBasicQuery { + params: JsonObjectBasicParams; + result: IJsonObjectBasicResult; +} + +export type JsonArrayAggregationParams = []; + +export interface IJsonArrayAggregationResult { + items: any; + rarity: string | null; +} + +export interface IJsonArrayAggregationQuery { + params: JsonArrayAggregationParams; + result: IJsonArrayAggregationResult; +} + +export type JsonOperatorsSelectParams = []; + +export interface IJsonOperatorsSelectResult { + extractedName: any; + id: number; + name: string; +} + +export interface IJsonOperatorsSelectQuery { + params: JsonOperatorsSelectParams; + result: IJsonOperatorsSelectResult; +} diff --git a/tests/demo/mysql/json_operations.snapshot.ts b/tests/demo/mysql/json_operations.snapshot.ts new file mode 100644 index 00000000..ec3e5c7d --- /dev/null +++ b/tests/demo/mysql/json_operations.snapshot.ts @@ -0,0 +1,36 @@ +export type JsonObjectBasicParams = []; + +export interface IJsonObjectBasicResult { + id: number; + itemJson: any; +} + +export interface IJsonObjectBasicQuery { + params: JsonObjectBasicParams; + result: IJsonObjectBasicResult; +} + +export type JsonArrayAggregationParams = []; + +export interface IJsonArrayAggregationResult { + items: any; + rarity: string | null; +} + +export interface IJsonArrayAggregationQuery { + params: JsonArrayAggregationParams; + result: IJsonArrayAggregationResult; +} + +export type JsonOperatorsSelectParams = []; + +export interface IJsonOperatorsSelectResult { + extractedName: any; + id: number; + name: string; +} + +export interface IJsonOperatorsSelectQuery { + params: JsonOperatorsSelectParams; + result: IJsonOperatorsSelectResult; +} diff --git a/tests/demo/mysql/json_operations.ts b/tests/demo/mysql/json_operations.ts new file mode 100644 index 00000000..36b84a33 --- /dev/null +++ b/tests/demo/mysql/json_operations.ts @@ -0,0 +1,40 @@ +import { sql } from 'sqlx-ts' + +// JSON_OBJECT basic - build object from columns +const jsonObjectBasic = sql` +-- @db: db_mysql +-- @db: db_mysql +-- @name: json object basic +SELECT + items.id AS id, + JSON_OBJECT('id', items.id, 'name', items.name, 'rarity', items.rarity) AS item_json +FROM items +` + +// JSON_ARRAYAGG for aggregation - aggregate rows into JSON array +const jsonArrayAggregation = sql` +-- @db: db_mysql +-- @db: db_mysql +-- @name: json array aggregation +SELECT + items.rarity AS rarity, + JSON_ARRAYAGG(JSON_OBJECT('id', items.id, 'name', items.name)) AS items +FROM items +GROUP BY items.rarity +` + +// JSON operators in SELECT - extract values +const jsonOperatorsSelect = sql` +-- @db: db_mysql +-- @db: db_mysql +-- @name: json operators select +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT( + JSON_OBJECT('id', json_test_data.id, 'name', json_test_data.name), + '$.name' + )) AS extracted_name +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` diff --git a/tests/demo/postgres/json_access_operators.queries.ts b/tests/demo/postgres/json_access_operators.queries.ts new file mode 100644 index 00000000..2e1dde7c --- /dev/null +++ b/tests/demo/postgres/json_access_operators.queries.ts @@ -0,0 +1,129 @@ +export type JsonFieldAccessParams = []; + +export interface IJsonFieldAccessResult { + activeJson: string; + ageJson: string; + id: number; + name: string; + usernameJson: string; +} + +export interface IJsonFieldAccessQuery { + params: JsonFieldAccessParams; + result: IJsonFieldAccessResult; +} + +export type JsonFieldAccessTextParams = []; + +export interface IJsonFieldAccessTextResult { + active: boolean; + age: number; + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFieldAccessTextQuery { + params: JsonFieldAccessTextParams; + result: IJsonFieldAccessTextResult; +} + +export type JsonNestedAccessParams = []; + +export interface IJsonNestedAccessResult { + addressJson: string; + city: string; + cityJson: string; + id: number; + name: string; + street: string; + streetJson: string; + zipCode: string; +} + +export interface IJsonNestedAccessQuery { + params: JsonNestedAccessParams; + result: IJsonNestedAccessResult; +} + +export type JsonArrayAccessParams = []; + +export interface IJsonArrayAccessResult { + firstItemJson: number; + firstItemName: string; + firstItemPrice: string; + id: number; + itemsJson: string; + name: string; + secondItemJson: number; +} + +export interface IJsonArrayAccessQuery { + params: JsonArrayAccessParams; + result: IJsonArrayAccessResult; +} + +export type JsonPathAccessParams = []; + +export interface IJsonPathAccessResult { + firstItemJson: string; + firstItemName: string; + firstItemRarity: string; + id: number; + level: string; + levelJson: string; + name: string; +} + +export interface IJsonPathAccessQuery { + params: JsonPathAccessParams; + result: IJsonPathAccessResult; +} + +export type JsonDeepPathAccessParams = []; + +export interface IJsonDeepPathAccessResult { + darkMode: string; + dbHost: string; + dbHostJson: string; + dbPort: string; + emailNotifications: string; + id: number; + name: string; +} + +export interface IJsonDeepPathAccessQuery { + params: JsonDeepPathAccessParams; + result: IJsonDeepPathAccessResult; +} + +export type JsonFilterByFieldParams = []; + +export interface IJsonFilterByFieldResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFilterByFieldQuery { + params: JsonFilterByFieldParams; + result: IJsonFilterByFieldResult; +} + +export type JsonNullHandlingParams = []; + +export interface IJsonNullHandlingResult { + firstComment: string; + firstReviewer: string; + id: number; + secondComment: string; + thirdComment: string; + thirdReviewer: string; +} + +export interface IJsonNullHandlingQuery { + params: JsonNullHandlingParams; + result: IJsonNullHandlingResult; +} diff --git a/tests/demo/postgres/json_access_operators.snapshot.ts b/tests/demo/postgres/json_access_operators.snapshot.ts new file mode 100644 index 00000000..eb972c33 --- /dev/null +++ b/tests/demo/postgres/json_access_operators.snapshot.ts @@ -0,0 +1,129 @@ +export type JsonFieldAccessParams = []; + +export interface IJsonFieldAccessResult { + active_json: string; + age_json: string; + id: number; + name: string; + username_json: string; +} + +export interface IJsonFieldAccessQuery { + params: JsonFieldAccessParams; + result: IJsonFieldAccessResult; +} + +export type JsonFieldAccessTextParams = []; + +export interface IJsonFieldAccessTextResult { + active: boolean; + age: number; + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFieldAccessTextQuery { + params: JsonFieldAccessTextParams; + result: IJsonFieldAccessTextResult; +} + +export type JsonNestedAccessParams = []; + +export interface IJsonNestedAccessResult { + address_json: string; + city: string; + city_json: string; + id: number; + name: string; + street: string; + street_json: string; + zip_code: string; +} + +export interface IJsonNestedAccessQuery { + params: JsonNestedAccessParams; + result: IJsonNestedAccessResult; +} + +export type JsonArrayAccessParams = []; + +export interface IJsonArrayAccessResult { + first_item_json: number; + first_item_name: string; + first_item_price: string; + id: number; + items_json: string; + name: string; + second_item_json: number; +} + +export interface IJsonArrayAccessQuery { + params: JsonArrayAccessParams; + result: IJsonArrayAccessResult; +} + +export type JsonPathAccessParams = []; + +export interface IJsonPathAccessResult { + first_item_json: string; + first_item_name: string; + first_item_rarity: string; + id: number; + level: string; + level_json: string; + name: string; +} + +export interface IJsonPathAccessQuery { + params: JsonPathAccessParams; + result: IJsonPathAccessResult; +} + +export type JsonDeepPathAccessParams = []; + +export interface IJsonDeepPathAccessResult { + dark_mode: string; + db_host: string; + db_host_json: string; + db_port: string; + email_notifications: string; + id: number; + name: string; +} + +export interface IJsonDeepPathAccessQuery { + params: JsonDeepPathAccessParams; + result: IJsonDeepPathAccessResult; +} + +export type JsonFilterByFieldParams = []; + +export interface IJsonFilterByFieldResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFilterByFieldQuery { + params: JsonFilterByFieldParams; + result: IJsonFilterByFieldResult; +} + +export type JsonNullHandlingParams = []; + +export interface IJsonNullHandlingResult { + first_comment: string; + first_reviewer: string; + id: number; + second_comment: string; + third_comment: string; + third_reviewer: string; +} + +export interface IJsonNullHandlingQuery { + params: JsonNullHandlingParams; + result: IJsonNullHandlingResult; +} diff --git a/tests/demo/postgres/json_access_operators.ts b/tests/demo/postgres/json_access_operators.ts new file mode 100644 index 00000000..79aab8a2 --- /dev/null +++ b/tests/demo/postgres/json_access_operators.ts @@ -0,0 +1,116 @@ +import { sql } from 'sqlx-ts' + +// Test -> operator (get JSON object field as JSON) +const jsonFieldAccess = sql` +-- @name: json field access +SELECT + id, + name, + data -> 'username' AS username_json, + data -> 'age' AS age_json, + data -> 'active' AS active_json +FROM json_test_data +WHERE name = 'user_profile' +` + +// Test ->> operator (get JSON object field as text) +const jsonFieldAccessText = sql` +-- @name: json field access text +SELECT + id, + name, + data ->> 'username' AS username, + data ->> 'email' AS email, + (data ->> 'age')::integer AS age, + (data ->> 'active')::boolean AS active +FROM json_test_data +WHERE name = 'user_profile' +` + +// Test nested field access with -> and ->> +const jsonNestedAccess = sql` +-- @name: json nested access +SELECT + id, + name, + data -> 'address' AS address_json, + data -> 'address' -> 'city' AS city_json, + data -> 'address' ->> 'city' AS city, + data -> 'address' ->> 'zipCode' AS zip_code, + data #> '{address, street}' AS street_json, + data #>> '{address, street}' AS street +FROM json_test_data +WHERE name = 'user_with_address' +` + +// Test array element access by index +const jsonArrayAccess = sql` +-- @name: json array access +SELECT + id, + name, + data -> 'items' AS items_json, + data -> 'items' -> 0 AS first_item_json, + data -> 'items' -> 1 AS second_item_json, + data -> 'items' -> 0 ->> 'name' AS first_item_name, + data -> 'items' -> 0 ->> 'price' AS first_item_price +FROM json_test_data +WHERE name = 'shopping_cart' +` + +// Test #> operator (get JSON object at path) +const jsonPathAccess = sql` +-- @name: json path access +SELECT + id, + name, + data #> '{stats, level}' AS level_json, + data #> '{stats, inventory, 0, item}' AS first_item_json, + data #>> '{stats, level}' AS level, + data #>> '{stats, inventory, 0, item}' AS first_item_name, + data #>> '{stats, inventory, 0, rarity}' AS first_item_rarity +FROM json_test_data +WHERE name = 'game_stats' +` + +// Test deep nested path access +const jsonDeepPathAccess = sql` +-- @name: json deep path access +SELECT + id, + name, + data #> '{app, settings, database, host}' AS db_host_json, + data #>> '{app, settings, database, host}' AS db_host, + data #>> '{app, settings, database, port}' AS db_port, + data #>> '{app, settings, features, darkMode}' AS dark_mode, + data #>> '{app, settings, features, notifications, email}' AS email_notifications +FROM json_test_data +WHERE name = 'nested_config' +` + +// Test mixed operators in WHERE clause +const jsonFilterByField = sql` +-- @name: json filter by field +SELECT + id, + name, + data ->> 'username' AS username, + data ->> 'email' AS email +FROM json_test_data +WHERE data ->> 'active' = 'true' + AND (data ->> 'age')::integer > 25 +` + +// Test null handling in JSON +const jsonNullHandling = sql` +-- @name: json null handling +SELECT + id, + data -> 'reviews' -> 0 ->> 'comment' AS first_comment, + data -> 'reviews' -> 1 ->> 'comment' AS second_comment, + data -> 'reviews' -> 2 ->> 'comment' AS third_comment, + data -> 'reviews' -> 0 ->> 'reviewer' AS first_reviewer, + data -> 'reviews' -> 2 ->> 'reviewer' AS third_reviewer +FROM json_test_data +WHERE name = 'product_reviews' +` diff --git a/tests/demo/postgres/json_array_functions.queries.ts b/tests/demo/postgres/json_array_functions.queries.ts new file mode 100644 index 00000000..cd1e5fed --- /dev/null +++ b/tests/demo/postgres/json_array_functions.queries.ts @@ -0,0 +1,26 @@ +export type JsonbArrayLengthParams = []; + +export interface IJsonbArrayLengthResult { + itemsCount: any; + jsonTestDataId: number; + jsonTestDataName: string; + tagsCount: any; +} + +export interface IJsonbArrayLengthQuery { + params: JsonbArrayLengthParams; + result: IJsonbArrayLengthResult; +} + +export type JsonbArrayContainsParams = []; + +export interface IJsonbArrayContainsResult { + jsonTestDataId: number; + jsonTestDataName: string; + tags: string; +} + +export interface IJsonbArrayContainsQuery { + params: JsonbArrayContainsParams; + result: IJsonbArrayContainsResult; +} diff --git a/tests/demo/postgres/json_array_functions.snapshot.ts b/tests/demo/postgres/json_array_functions.snapshot.ts new file mode 100644 index 00000000..cd1e5fed --- /dev/null +++ b/tests/demo/postgres/json_array_functions.snapshot.ts @@ -0,0 +1,26 @@ +export type JsonbArrayLengthParams = []; + +export interface IJsonbArrayLengthResult { + itemsCount: any; + jsonTestDataId: number; + jsonTestDataName: string; + tagsCount: any; +} + +export interface IJsonbArrayLengthQuery { + params: JsonbArrayLengthParams; + result: IJsonbArrayLengthResult; +} + +export type JsonbArrayContainsParams = []; + +export interface IJsonbArrayContainsResult { + jsonTestDataId: number; + jsonTestDataName: string; + tags: string; +} + +export interface IJsonbArrayContainsQuery { + params: JsonbArrayContainsParams; + result: IJsonbArrayContainsResult; +} diff --git a/tests/demo/postgres/json_array_functions.ts b/tests/demo/postgres/json_array_functions.ts new file mode 100644 index 00000000..1a27feb4 --- /dev/null +++ b/tests/demo/postgres/json_array_functions.ts @@ -0,0 +1,25 @@ +import { sql } from 'sqlx-ts' + +// Test jsonb_array_length - get array length +const jsonbArrayLength = sql` +-- @name: jsonb array length +SELECT + json_test_data.id, + json_test_data.name, + jsonb_array_length(data -> 'items') AS items_count, + jsonb_array_length(data -> 'tags') AS tags_count +FROM json_test_data +WHERE json_test_data.name IN ('shopping_cart', 'tags') +` + +// Test array contains using @> operator +const jsonbArrayContains = sql` +-- @name: jsonb array contains +SELECT + json_test_data.id, + json_test_data.name, + data -> 'tags' AS tags +FROM json_test_data +WHERE data -> 'tags' @> '["postgresql"]'::jsonb +` + diff --git a/tests/demo/postgres/json_comprehensive.queries.ts b/tests/demo/postgres/json_comprehensive.queries.ts new file mode 100644 index 00000000..8c2e6431 --- /dev/null +++ b/tests/demo/postgres/json_comprehensive.queries.ts @@ -0,0 +1,136 @@ +export type JsonAccessOperatorsParams = []; + +export interface IJsonAccessOperatorsResult { + age: number; + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonAccessOperatorsQuery { + params: JsonAccessOperatorsParams; + result: IJsonAccessOperatorsResult; +} + +export type JsonNestedAccessParams = []; + +export interface IJsonNestedAccessResult { + city: string; + id: number; + name: string; + zipCode: string; +} + +export interface IJsonNestedAccessQuery { + params: JsonNestedAccessParams; + result: IJsonNestedAccessResult; +} + +export type JsonArrayIndexParams = []; + +export interface IJsonArrayIndexResult { + firstItemName: string; + firstItemPrice: number; + id: number; + name: string; +} + +export interface IJsonArrayIndexQuery { + params: JsonArrayIndexParams; + result: IJsonArrayIndexResult; +} + +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonTypeofParams = []; + +export interface IJsonTypeofResult { + ageType: any; + id: number; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeofQuery { + params: JsonTypeofParams; + result: IJsonTypeofResult; +} + +export type JsonKeyExistsParams = []; + +export interface IJsonKeyExistsResult { + hasAddress: string; + hasUsername: string; + id: number; + name: string; +} + +export interface IJsonKeyExistsQuery { + params: JsonKeyExistsParams; + result: IJsonKeyExistsResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + id: number; + isActive: string; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonBuildObjectTypedParams = []; + +export interface IJsonBuildObjectTypedResult { + id: number; + name: string; + userSummary: { id: number; name: string; username: any; email: any }; +} + +export interface IJsonBuildObjectTypedQuery { + params: JsonBuildObjectTypedParams; + result: IJsonBuildObjectTypedResult; +} + +export type JsonFilterParams = []; + +export interface IJsonFilterResult { + id: number; + name: string; + username: string; +} + +export interface IJsonFilterQuery { + params: JsonFilterParams; + result: IJsonFilterResult; +} + +export type JsonDeepPathParams = []; + +export interface IJsonDeepPathResult { + appName: string; + dbHost: string; + dbPort: number; + id: number; +} + +export interface IJsonDeepPathQuery { + params: JsonDeepPathParams; + result: IJsonDeepPathResult; +} diff --git a/tests/demo/postgres/json_comprehensive.snapshot.ts b/tests/demo/postgres/json_comprehensive.snapshot.ts new file mode 100644 index 00000000..90455fdf --- /dev/null +++ b/tests/demo/postgres/json_comprehensive.snapshot.ts @@ -0,0 +1,136 @@ +export type JsonAccessOperatorsParams = []; + +export interface IJsonAccessOperatorsResult { + age: number; + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonAccessOperatorsQuery { + params: JsonAccessOperatorsParams; + result: IJsonAccessOperatorsResult; +} + +export type JsonNestedAccessParams = []; + +export interface IJsonNestedAccessResult { + city: string; + id: number; + name: string; + zip_code: string; +} + +export interface IJsonNestedAccessQuery { + params: JsonNestedAccessParams; + result: IJsonNestedAccessResult; +} + +export type JsonArrayIndexParams = []; + +export interface IJsonArrayIndexResult { + first_item_name: string; + first_item_price: number; + id: number; + name: string; +} + +export interface IJsonArrayIndexQuery { + params: JsonArrayIndexParams; + result: IJsonArrayIndexResult; +} + +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + name: string; + tags_count: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonTypeofParams = []; + +export interface IJsonTypeofResult { + age_type: any; + id: number; + tags_type: any; + username_type: any; +} + +export interface IJsonTypeofQuery { + params: JsonTypeofParams; + result: IJsonTypeofResult; +} + +export type JsonKeyExistsParams = []; + +export interface IJsonKeyExistsResult { + has_address: string; + has_username: string; + id: number; + name: string; +} + +export interface IJsonKeyExistsQuery { + params: JsonKeyExistsParams; + result: IJsonKeyExistsResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + id: number; + is_active: string; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonBuildObjectTypedParams = []; + +export interface IJsonBuildObjectTypedResult { + id: number; + name: string; + user_summary: { id: number; name: string; username: any; email: any }; +} + +export interface IJsonBuildObjectTypedQuery { + params: JsonBuildObjectTypedParams; + result: IJsonBuildObjectTypedResult; +} + +export type JsonFilterParams = []; + +export interface IJsonFilterResult { + id: number; + name: string; + username: string; +} + +export interface IJsonFilterQuery { + params: JsonFilterParams; + result: IJsonFilterResult; +} + +export type JsonDeepPathParams = []; + +export interface IJsonDeepPathResult { + app_name: string; + db_host: string; + db_port: number; + id: number; +} + +export interface IJsonDeepPathQuery { + params: JsonDeepPathParams; + result: IJsonDeepPathResult; +} diff --git a/tests/demo/postgres/json_comprehensive.ts b/tests/demo/postgres/json_comprehensive.ts new file mode 100644 index 00000000..207ec0fb --- /dev/null +++ b/tests/demo/postgres/json_comprehensive.ts @@ -0,0 +1,124 @@ +import { sql } from 'sqlx-ts' + +// Test 1: JSON access operators -> and ->> +const jsonAccessOperators = sql` +-- @name: json access operators +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data ->> 'username' AS username, + data ->> 'email' AS email, + (data ->> 'age')::integer AS age +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 2: Nested JSON access with #> and #>> +const jsonNestedAccess = sql` +-- @name: json nested access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data #>> '{address, city}' AS city, + data #>> '{address, zipCode}' AS zip_code +FROM json_test_data +WHERE json_test_data.name = 'user_with_address' +` + +// Test 3: JSON array access by index +const jsonArrayIndex = sql` +-- @name: json array index +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data -> 'items' -> 0 ->> 'name' AS first_item_name, + (data -> 'items' -> 0 ->> 'price')::numeric AS first_item_price +FROM json_test_data +WHERE json_test_data.name = 'shopping_cart' +` + +// Test 4: JSON array length +const jsonArrayLength = sql` +-- @name: json array length +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + jsonb_array_length(data -> 'tags') AS tags_count +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test 5: JSON typeof +const jsonTypeof = sql` +-- @name: json typeof +SELECT + json_test_data.id AS id, + jsonb_typeof(data -> 'username') AS username_type, + jsonb_typeof(data -> 'age') AS age_type, + jsonb_typeof(data -> 'tags') AS tags_type +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'tags') +` + +// Test 6: JSON key existence with ? +const jsonKeyExists = sql` +-- @name: json key exists +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + (data ? 'username')::text AS has_username, + (data ? 'address')::text AS has_address +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +` + +// Test 7: JSON containment with @> +const jsonContains = sql` +-- @name: json contains +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + (data @> '{"active": true}'::jsonb)::text AS is_active +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 8: JSON build object with typed fields +const jsonBuildObjectTyped = sql` +-- @name: json build object typed +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + jsonb_build_object( + 'id', json_test_data.id, + 'name', json_test_data.name, + 'username', data ->> 'username', + 'email', data ->> 'email' + ) AS user_summary +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 9: Filter using JSON operators +const jsonFilter = sql` +-- @name: json filter +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data ->> 'username' AS username +FROM json_test_data +WHERE (data ->> 'active')::boolean = true + AND (data ->> 'age')::integer > 25 +` + +// Test 10: JSON path queries +const jsonDeepPath = sql` +-- @name: json deep path +SELECT + json_test_data.id AS id, + data #>> '{app, name}' AS app_name, + data #>> '{app, settings, database, host}' AS db_host, + (data #>> '{app, settings, database, port}')::integer AS db_port +FROM json_test_data +WHERE json_test_data.name = 'nested_config' +` diff --git a/tests/demo/postgres/json_object_functions.queries.ts b/tests/demo/postgres/json_object_functions.queries.ts new file mode 100644 index 00000000..fb2fc6d6 --- /dev/null +++ b/tests/demo/postgres/json_object_functions.queries.ts @@ -0,0 +1,142 @@ +export type JsonbObjectKeysParams = []; + +export interface IJsonbObjectKeysResult { + jsonTestDataId: number; + jsonTestDataName: string; + objectKey: any; +} + +export interface IJsonbObjectKeysQuery { + params: JsonbObjectKeysParams; + result: IJsonbObjectKeysResult; +} + +export type JsonbTypeofParams = []; + +export interface IJsonbTypeofResult { + activeType: any; + ageType: any; + itemsType: any; + jsonTestDataId: number; + jsonTestDataName: string; + tagsType: any; + usernameType: any; +} + +export interface IJsonbTypeofQuery { + params: JsonbTypeofParams; + result: IJsonbTypeofResult; +} + +export type JsonbStripNullsParams = []; + +export interface IJsonbStripNullsResult { + jsonTestDataId: number; + jsonTestDataName: string; + reviewWithNulls: number; + reviewWithoutNulls: any; +} + +export interface IJsonbStripNullsQuery { + params: JsonbStripNullsParams; + result: IJsonbStripNullsResult; +} + +export type JsonbKeyExistsParams = []; + +export interface IJsonbKeyExistsResult { + hasAddress: string; + hasNonexistent: string; + hasUsername: string; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbKeyExistsQuery { + params: JsonbKeyExistsParams; + result: IJsonbKeyExistsResult; +} + +export type JsonbAnyKeyExistsParams = []; + +export interface IJsonbAnyKeyExistsResult { + hasAnyContact: any; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbAnyKeyExistsQuery { + params: JsonbAnyKeyExistsParams; + result: IJsonbAnyKeyExistsResult; +} + +export type JsonbAllKeysExistParams = []; + +export interface IJsonbAllKeysExistResult { + hasAllRequired: any; + hasAllWithPhone: any; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbAllKeysExistQuery { + params: JsonbAllKeysExistParams; + result: IJsonbAllKeysExistResult; +} + +export type JsonbContainsParams = []; + +export interface IJsonbContainsResult { + hasSpecificUsername: object; + isActive: object; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbContainsQuery { + params: JsonbContainsParams; + result: IJsonbContainsResult; +} + +export type JsonbContainedByParams = []; + +export interface IJsonbContainedByResult { + jsonTestDataId: number; + jsonTestDataName: string; + subsetInData: object; + usernameInData: object; +} + +export interface IJsonbContainedByQuery { + params: JsonbContainedByParams; + result: IJsonbContainedByResult; +} + +export type JsonbSetParams = []; + +export interface IJsonbSetResult { + jsonTestDataId: number; + jsonTestDataName: string; + originalData: object; + updatedAge: any; + updatedCity: any; +} + +export interface IJsonbSetQuery { + params: JsonbSetParams; + result: IJsonbSetResult; +} + +export type JsonbInsertParams = []; + +export interface IJsonbInsertResult { + jsonTestDataId: number; + jsonTestDataName: string; + originalData: object; + withPhone: any; +} + +export interface IJsonbInsertQuery { + params: JsonbInsertParams; + result: IJsonbInsertResult; +} diff --git a/tests/demo/postgres/json_object_functions.snapshot.ts b/tests/demo/postgres/json_object_functions.snapshot.ts new file mode 100644 index 00000000..5679e9f9 --- /dev/null +++ b/tests/demo/postgres/json_object_functions.snapshot.ts @@ -0,0 +1,143 @@ +export type JsonbObjectKeysParams = []; + +export interface IJsonbObjectKeysResult { + jsonTestDataId: number; + jsonTestDataName: string; + objectKey: any; +} + +export interface IJsonbObjectKeysQuery { + params: JsonbObjectKeysParams; + result: IJsonbObjectKeysResult; +} + +export type JsonbTypeofParams = []; + +export interface IJsonbTypeofResult { + activeType: any; + ageType: any; + itemsType: any; + jsonTestDataId: number; + jsonTestDataName: string; + tagsType: any; + usernameType: any; +} + +export interface IJsonbTypeofQuery { + params: JsonbTypeofParams; + result: IJsonbTypeofResult; +} + +export type JsonbStripNullsParams = []; + +export interface IJsonbStripNullsResult { + jsonTestDataId: number; + jsonTestDataName: string; + reviewWithNulls: number; + reviewWithoutNulls: any; +} + +export interface IJsonbStripNullsQuery { + params: JsonbStripNullsParams; + result: IJsonbStripNullsResult; +} + +export type JsonbKeyExistsParams = []; + +export interface IJsonbKeyExistsResult { + hasAddress: string; + hasNonexistent: string; + hasUsername: string; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbKeyExistsQuery { + params: JsonbKeyExistsParams; + result: IJsonbKeyExistsResult; +} + +export type JsonbAnyKeyExistsParams = []; + +export interface IJsonbAnyKeyExistsResult { + hasAnyContact: any; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbAnyKeyExistsQuery { + params: JsonbAnyKeyExistsParams; + result: IJsonbAnyKeyExistsResult; +} + +export type JsonbAllKeysExistParams = []; + +export interface IJsonbAllKeysExistResult { + hasAllRequired: any; + hasAllWithPhone: any; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbAllKeysExistQuery { + params: JsonbAllKeysExistParams; + result: IJsonbAllKeysExistResult; +} + +export type JsonbContainsParams = []; + +export interface IJsonbContainsResult { + hasSpecificUsername: object; + isActive: object; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbContainsQuery { + params: JsonbContainsParams; + result: IJsonbContainsResult; +} + +export type JsonbContainedByParams = []; + +export interface IJsonbContainedByResult { + jsonTestDataId: number; + jsonTestDataName: string; + subsetInData: object; + usernameInData: object; +} + +export interface IJsonbContainedByQuery { + params: JsonbContainedByParams; + result: IJsonbContainedByResult; +} + +export type JsonbSetParams = []; + +export interface IJsonbSetResult { + jsonTestDataId: number; + jsonTestDataName: string; + originalData: object; + updatedAge: any; + updatedCity: any; +} + +export interface IJsonbSetQuery { + params: JsonbSetParams; + result: IJsonbSetResult; +} + +export type JsonbInsertParams = []; + +export interface IJsonbInsertResult { + jsonTestDataId: number; + jsonTestDataName: string; + originalData: object; + withPhone: any; +} + +export interface IJsonbInsertQuery { + params: JsonbInsertParams; + result: IJsonbInsertResult; +} + diff --git a/tests/demo/postgres/json_object_functions.ts b/tests/demo/postgres/json_object_functions.ts new file mode 100644 index 00000000..9a9d1fd2 --- /dev/null +++ b/tests/demo/postgres/json_object_functions.ts @@ -0,0 +1,126 @@ +import { sql } from 'sqlx-ts' + +// Test jsonb_object_keys - get all keys from object +const jsonbObjectKeys = sql` +-- @name: jsonb object keys +SELECT + json_test_data.id, + json_test_data.name, + jsonb_object_keys(json_test_data.data) AS object_key +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test jsonb_typeof - get type of JSON value +const jsonbTypeof = sql` +-- @name: jsonb typeof +SELECT + json_test_data.id, + json_test_data.name, + jsonb_typeof(json_test_data.data -> 'username') AS username_type, + jsonb_typeof(json_test_data.data -> 'age') AS age_type, + jsonb_typeof(json_test_data.data -> 'active') AS active_type, + jsonb_typeof(json_test_data.data -> 'items') AS items_type, + jsonb_typeof(json_test_data.data -> 'tags') AS tags_type +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'shopping_cart', 'tags') +` + +// Test jsonb_strip_nulls - remove null values +const jsonbStripNulls = sql` +-- @name: jsonb strip nulls +SELECT + json_test_data.id, + json_test_data.name, + json_test_data.data -> 'reviews' -> 1 AS review_with_nulls, + jsonb_strip_nulls(json_test_data.data -> 'reviews' -> 1) AS review_without_nulls +FROM json_test_data +WHERE json_test_data.name = 'product_reviews' +` + +// Test ? operator - key exists +const jsonbKeyExists = sql` +-- @name: jsonb key exists +SELECT + json_test_data.id, + json_test_data.name, + json_test_data.data ? 'username' AS has_username, + json_test_data.data ? 'address' AS has_address, + json_test_data.data ? 'nonexistent' AS has_nonexistent +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +` + +// Test ?| operator - any key exists +const jsonbAnyKeyExists = sql` +-- @name: jsonb any key exists +SELECT + json_test_data.id, + json_test_data.name, + json_test_data.data ?| array['username', 'email', 'phone'] AS has_any_contact +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +` + +// Test ?& operator - all keys exist +const jsonbAllKeysExist = sql` +-- @name: jsonb all keys exist +SELECT + json_test_data.id, + json_test_data.name, + json_test_data.data ?& array['username', 'email'] AS has_all_required, + json_test_data.data ?& array['username', 'email', 'phone'] AS has_all_with_phone +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +` + +// Test @> operator - contains (left contains right) +const jsonbContains = sql` +-- @name: jsonb contains +SELECT + json_test_data.id, + json_test_data.name, + json_test_data.data @> '{"username": "john_doe"}'::jsonb AS has_specific_username, + json_test_data.data @> '{"active": true}'::jsonb AS is_active +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test <@ operator - is contained by (left is contained in right) +const jsonbContainedBy = sql` +-- @name: jsonb contained by +SELECT + json_test_data.id, + json_test_data.name, + '{"username": "john_doe"}'::jsonb <@ json_test_data.data AS username_in_data, + '{"username": "john_doe", "age": 30}'::jsonb <@ json_test_data.data AS subset_in_data +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test jsonb_set - update value in JSON +const jsonbSet = sql` +-- @name: jsonb set +SELECT + json_test_data.id, + json_test_data.name, + json_test_data.data AS original_data, + jsonb_set(json_test_data.data, '{age}', '31') AS updated_age, + jsonb_set(json_test_data.data, '{address, city}', '"New York"') AS updated_city +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +LIMIT 2 +` + +// Test jsonb_insert - insert value into JSON +const jsonbInsert = sql` +-- @name: jsonb insert +SELECT + json_test_data.id, + json_test_data.name, + json_test_data.data AS original_data, + jsonb_insert(json_test_data.data, '{phone}', '"555-1234"') AS with_phone +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + From 9c4aae91f69d8c90e6b3b60672d2d47af8622a1e Mon Sep 17 00:00:00 2001 From: JasonShin Date: Fri, 6 Mar 2026 23:06:13 +1100 Subject: [PATCH 03/19] refactor --- .../function_handlers/json_functions.rs | 203 ++++++++++ .../expressions/function_handlers/mod.rs | 2 + .../polymorphic_functions.rs | 93 +++++ .../sql_parser/expressions/mod.rs | 2 + .../sql_parser/expressions/translate_expr.rs | 373 ++---------------- 5 files changed, 338 insertions(+), 335 deletions(-) create mode 100644 src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs create mode 100644 src/ts_generator/sql_parser/expressions/function_handlers/mod.rs create mode 100644 src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs new file mode 100644 index 00000000..fed13556 --- /dev/null +++ b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs @@ -0,0 +1,203 @@ +use crate::common::lazy::DB_SCHEMA; +use crate::core::connection::DBConn; +use crate::ts_generator::errors::TsGeneratorError; +use crate::ts_generator::sql_parser::expressions::translate_data_type::translate_value; +use crate::ts_generator::sql_parser::expressions::translate_table_with_joins::translate_table_from_expr; +use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; +use crate::ts_generator::types::ts_query::{TsFieldType, TsQuery}; +use sqlparser::ast::{Expr, FunctionArg, FunctionArgExpr, TableWithJoins, Value}; + +/// Extract key name from a function argument (should be a string literal) +fn extract_key_name(arg: &FunctionArg) -> Option { + match arg { + FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(val))) => match &val.value { + Value::SingleQuotedString(s) | Value::DoubleQuotedString(s) => Some(s.clone()), + _ => None, + }, + _ => None, + } +} + +/// Extract expression from a function argument +fn extract_expr_from_arg(arg: &FunctionArg) -> Option<&Expr> { + match arg { + FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), + FunctionArg::Named { + arg: FunctionArgExpr::Expr(expr), + .. + } => Some(expr), + _ => None, + } +} + +/// Infer the TypeScript type from an SQL expression +pub async fn infer_type_from_expr( + expr: &Expr, + single_table_name: &Option<&str>, + table_with_joins: &Option>, + db_conn: &DBConn, +) -> Option<(TsFieldType, bool)> { + match expr { + Expr::Identifier(ident) => { + let column_name = DisplayIndent(ident).to_string(); + if let Some(table_name) = single_table_name { + let table_details = DB_SCHEMA.lock().await.fetch_table(&vec![table_name], db_conn).await; + + if let Some(table_details) = table_details { + if let Some(field) = table_details.get(&column_name) { + Some((field.field_type.to_owned(), field.is_nullable)) + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } + Expr::CompoundIdentifier(idents) if idents.len() == 2 => { + let column_name = DisplayIndent(&idents[1]).to_string(); + if let Ok(table_name) = translate_table_from_expr(table_with_joins, expr) { + let table_details = DB_SCHEMA + .lock() + .await + .fetch_table(&vec![table_name.as_str()], db_conn) + .await; + + if let Some(table_details) = table_details { + if let Some(field) = table_details.get(&column_name) { + Some((field.field_type.to_owned(), field.is_nullable)) + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } else { + Some((TsFieldType::Any, false)) + } + } + Expr::Value(val) => { + if let Some(ts_field_type) = translate_value(&val.value) { + Some((ts_field_type, false)) + } else { + Some((TsFieldType::Any, false)) + } + } + _ => Some((TsFieldType::Any, false)), + } +} + +/// Process key-value pairs from JSON build object arguments +pub async fn process_json_build_object_args( + args: &[FunctionArg], + single_table_name: &Option<&str>, + table_with_joins: &Option>, + db_conn: &DBConn, +) -> Option> { + if args.len() % 2 != 0 { + // Invalid number of arguments + return None; + } + + let mut object_fields = vec![]; + + // Process key-value pairs + for i in (0..args.len()).step_by(2) { + let key_arg = &args[i]; + let value_arg = &args[i + 1]; + + // Extract key name + let key_name = extract_key_name(key_arg)?; + + // Extract value expression + let value_expr = extract_expr_from_arg(value_arg)?; + + // Infer value type + let (value_type, is_nullable) = infer_type_from_expr(value_expr, single_table_name, table_with_joins, db_conn).await?; + + object_fields.push((key_name, value_type, is_nullable)); + } + + Some(object_fields) +} + +/// Handle JSON build functions (jsonb_build_object, json_build_object, etc.) +pub async fn handle_json_build_function( + function_name: &str, + args: &[FunctionArg], + single_table_name: &Option<&str>, + table_with_joins: &Option>, + db_conn: &DBConn, + alias: &str, + ts_query: &mut TsQuery, + is_selection: bool, + expr_for_logging: Option<&str>, +) -> Result<(), TsGeneratorError> { + let expr_log = expr_for_logging.unwrap_or(""); + + // Handle jsonb_build_object / json_build_object + if function_name.to_uppercase() == "JSONB_BUILD_OBJECT" || function_name.to_uppercase() == "JSON_BUILD_OBJECT" { + if let Some(object_fields) = process_json_build_object_args(args, single_table_name, table_with_joins, db_conn).await + { + let object_type = TsFieldType::StructuredObject(object_fields); + return ts_query.insert_result(Some(alias), &[object_type], is_selection, false, expr_log); + } + } + + // For other build functions or on failure, return Any + ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_log) +} + +/// Handle JSON aggregation functions (jsonb_agg, json_agg, etc.) +pub async fn handle_json_agg_function( + args: &[FunctionArg], + single_table_name: &Option<&str>, + table_with_joins: &Option>, + db_conn: &DBConn, + alias: &str, + ts_query: &mut TsQuery, + is_selection: bool, + expr_for_logging: Option<&str>, +) -> Result<(), TsGeneratorError> { + use super::super::functions::is_json_build_function; + use sqlparser::ast::FunctionArguments; + + let expr_log = expr_for_logging.unwrap_or(""); + + // jsonb_agg typically takes a single expression + if args.len() != 1 { + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_log); + } + + let arg_expr = extract_expr_from_arg(&args[0]); + + if let Some(arg_expr) = arg_expr { + // Check if the argument is a jsonb_build_object function + if let Expr::Function(inner_func) = arg_expr { + let inner_func_name = inner_func.name.to_string(); + if is_json_build_function(inner_func_name.as_str()) { + // Extract arguments from the inner function + let inner_args = match &inner_func.args { + FunctionArguments::List(arg_list) => &arg_list.args, + _ => { + return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_log); + } + }; + + // Process the inner jsonb_build_object + if let Some(object_fields) = + process_json_build_object_args(inner_args, single_table_name, table_with_joins, db_conn).await + { + let object_type = TsFieldType::StructuredObject(object_fields); + let array_type = TsFieldType::Array(Box::new(object_type)); + return ts_query.insert_result(Some(alias), &[array_type], is_selection, false, expr_log); + } + } + } + } + + // If we can't infer the type, return Any + ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_log) +} diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs b/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs new file mode 100644 index 00000000..988b9f41 --- /dev/null +++ b/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs @@ -0,0 +1,2 @@ +pub mod json_functions; +pub mod polymorphic_functions; diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs new file mode 100644 index 00000000..198c5d0c --- /dev/null +++ b/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs @@ -0,0 +1,93 @@ +use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments, TableWithJoins}; +use crate::common::lazy::DB_SCHEMA; +use crate::ts_generator::errors::TsGeneratorError; +use crate::ts_generator::sql_parser::expressions::translate_data_type::translate_value; +use crate::ts_generator::sql_parser::expressions::translate_table_with_joins::translate_table_from_expr; +use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; +use crate::ts_generator::types::ts_query::{TsFieldType, TsQuery}; + +pub async fn handle_polymorphic_functions( + ts_query: &mut TsQuery, + single_table_name: &Option<&str>, + table_with_joins: &Option>, + alias: &str, + is_selection: bool, + expr_for_logging: &str, + func_obj: &Function, + db_conn: &crate::core::connection::DBConn, +) -> Result<(), TsGeneratorError> { + // In sqlparser 0.59.0, args is a FunctionArguments enum + // Extract the first argument from the appropriate variant + let first_arg = match &func_obj.args { + FunctionArguments::List(arg_list) => arg_list.args.first(), + FunctionArguments::None => None, + FunctionArguments::Subquery(_) => None, // Can't infer type from subquery easily + }; + + if let Some(first_arg) = first_arg { + let first_expr = match first_arg { + FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), + FunctionArg::Named { + arg: FunctionArgExpr::Expr(expr), + .. + } => Some(expr), + _ => None, + }; + + if let Some(arg_expr) = first_expr { + // Try to infer type from the first argument + match arg_expr { + Expr::Identifier(ident) => { + let column_name = DisplayIndent(ident).to_string(); + if let Some(table_name) = single_table_name { + let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], db_conn).await; + + if let Some(table_details) = table_details { + if let Some(field) = table_details.get(&column_name) { + return ts_query.insert_result( + Some(alias), + &[field.field_type.to_owned()], + is_selection, + false, // IFNULL/COALESCE removes nullability + expr_for_logging, + ); + } + } + } + } + Expr::CompoundIdentifier(idents) if idents.len() == 2 => { + let column_name = DisplayIndent(&idents[1]).to_string(); + if let Ok(table_name) = translate_table_from_expr(table_with_joins, arg_expr) { + let table_details = &DB_SCHEMA + .lock() + .await + .fetch_table(&vec![table_name.as_str()], db_conn) + .await; + + if let Some(table_details) = table_details { + if let Some(field) = table_details.get(&column_name) { + return ts_query.insert_result( + Some(alias), + &[field.field_type.to_owned()], + is_selection, + false, // IFNULL/COALESCE removes nullability + expr_for_logging, + ); + } + } + } + } + Expr::Value(val) => { + // If first arg is a literal value, infer from that + if let Some(ts_field_type) = translate_value(&val.value) { + return ts_query.insert_result(Some(alias), &[ts_field_type], is_selection, false, expr_for_logging); + } + } + _ => {} + } + } + } + + // Fallback to Any if we couldn't infer the type + ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging) +} diff --git a/src/ts_generator/sql_parser/expressions/mod.rs b/src/ts_generator/sql_parser/expressions/mod.rs index 81d8188f..be4f30b1 100644 --- a/src/ts_generator/sql_parser/expressions/mod.rs +++ b/src/ts_generator/sql_parser/expressions/mod.rs @@ -4,6 +4,8 @@ pub mod translate_expr; pub mod translate_table_with_joins; pub mod translate_wildcard_expr; +pub mod function_handlers; + #[cfg(test)] #[path = "./functions.test.rs"] mod functions_test; diff --git a/src/ts_generator/sql_parser/expressions/translate_expr.rs b/src/ts_generator/sql_parser/expressions/translate_expr.rs index a7fea063..6821ba1e 100644 --- a/src/ts_generator/sql_parser/expressions/translate_expr.rs +++ b/src/ts_generator/sql_parser/expressions/translate_expr.rs @@ -1,3 +1,5 @@ +use super::function_handlers::json_functions::{handle_json_agg_function, handle_json_build_function}; +use super::function_handlers::polymorphic_functions::handle_polymorphic_functions; use super::functions::{is_date_function, is_json_agg_function, is_json_build_function, is_numeric_function, is_type_polymorphic_function}; use crate::common::lazy::DB_SCHEMA; use crate::common::logger::{error, warning}; @@ -629,87 +631,22 @@ pub async fn translate_expr( // Handle type-polymorphic functions (IFNULL, COALESCE, etc.) // These functions return the type of their first argument if is_type_polymorphic_function(function_name_str) { - use sqlparser::ast::{FunctionArg, FunctionArgExpr, FunctionArguments}; - - // In sqlparser 0.59.0, args is a FunctionArguments enum - // Extract the first argument from the appropriate variant - let first_arg = match &func_obj.args { - FunctionArguments::List(arg_list) => arg_list.args.first(), - FunctionArguments::None => None, - FunctionArguments::Subquery(_) => None, // Can't infer type from subquery easily - }; - - if let Some(first_arg) = first_arg { - let first_expr = match first_arg { - FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), - FunctionArg::Named { - arg: FunctionArgExpr::Expr(expr), - .. - } => Some(expr), - _ => None, - }; - - if let Some(arg_expr) = first_expr { - // Try to infer type from the first argument - match arg_expr { - Expr::Identifier(ident) => { - let column_name = DisplayIndent(ident).to_string(); - if let Some(table_name) = single_table_name { - let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], db_conn).await; - - if let Some(table_details) = table_details { - if let Some(field) = table_details.get(&column_name) { - return ts_query.insert_result( - Some(alias), - &[field.field_type.to_owned()], - is_selection, - false, // IFNULL/COALESCE removes nullability - expr_for_logging, - ); - } - } - } - } - Expr::CompoundIdentifier(idents) if idents.len() == 2 => { - let column_name = DisplayIndent(&idents[1]).to_string(); - if let Ok(table_name) = translate_table_from_expr(table_with_joins, arg_expr) { - let table_details = &DB_SCHEMA - .lock() - .await - .fetch_table(&vec![table_name.as_str()], db_conn) - .await; - - if let Some(table_details) = table_details { - if let Some(field) = table_details.get(&column_name) { - return ts_query.insert_result( - Some(alias), - &[field.field_type.to_owned()], - is_selection, - false, // IFNULL/COALESCE removes nullability - expr_for_logging, - ); - } - } - } - } - Expr::Value(val) => { - // If first arg is a literal value, infer from that - if let Some(ts_field_type) = translate_value(&val.value) { - return ts_query.insert_result(Some(alias), &[ts_field_type], is_selection, false, expr_for_logging); - } - } - _ => {} - } - } - } - - // Fallback to Any if we couldn't infer the type - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + return handle_polymorphic_functions( + ts_query, + single_table_name, + table_with_joins, + alias, + is_selection, + expr_for_logging, + func_obj, + db_conn, + ) + .await; } // Handle JSON build functions (jsonb_build_object, json_build_object, etc.) if is_json_build_function(function_name_str) { - use sqlparser::ast::{FunctionArg, FunctionArgExpr, FunctionArguments}; + use sqlparser::ast::FunctionArguments; let args = match &func_obj.args { FunctionArguments::List(arg_list) => &arg_list.args, @@ -719,126 +656,23 @@ pub async fn translate_expr( } }; - // jsonb_build_object takes key-value pairs - // e.g., jsonb_build_object('id', id, 'name', name) - if function_name_str.to_uppercase() == "JSONB_BUILD_OBJECT" || function_name_str.to_uppercase() == "JSON_BUILD_OBJECT" { - if args.len() % 2 != 0 { - // Invalid number of arguments - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - - let mut object_fields = vec![]; - - // Process key-value pairs - for i in (0..args.len()).step_by(2) { - let key_arg = &args[i]; - let value_arg = &args[i + 1]; - - // Extract key name (should be a string literal) - let key_name = match key_arg { - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(val))) => { - match &val.value { - Value::SingleQuotedString(s) | Value::DoubleQuotedString(s) => Some(s.clone()), - _ => None, - } - } - _ => None, - }; - - if key_name.is_none() { - // If we can't extract the key, return Any - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - - let key_name = key_name.unwrap(); - - // Extract value type from the expression - let value_expr = match value_arg { - FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), - FunctionArg::Named { - arg: FunctionArgExpr::Expr(expr), - .. - } => Some(expr), - _ => None, - }; - - if let Some(value_expr) = value_expr { - let value_type = match value_expr { - Expr::Identifier(ident) => { - let column_name = DisplayIndent(ident).to_string(); - if let Some(table_name) = single_table_name { - let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], db_conn).await; - - if let Some(table_details) = table_details { - if let Some(field) = table_details.get(&column_name) { - Some((field.field_type.to_owned(), field.is_nullable)) - } else { - Some((TsFieldType::Any, false)) - } - } else { - Some((TsFieldType::Any, false)) - } - } else { - Some((TsFieldType::Any, false)) - } - } - Expr::CompoundIdentifier(idents) if idents.len() == 2 => { - let column_name = DisplayIndent(&idents[1]).to_string(); - if let Ok(table_name) = translate_table_from_expr(table_with_joins, value_expr) { - let table_details = &DB_SCHEMA - .lock() - .await - .fetch_table(&vec![table_name.as_str()], db_conn) - .await; - - if let Some(table_details) = table_details { - if let Some(field) = table_details.get(&column_name) { - Some((field.field_type.to_owned(), field.is_nullable)) - } else { - Some((TsFieldType::Any, false)) - } - } else { - Some((TsFieldType::Any, false)) - } - } else { - Some((TsFieldType::Any, false)) - } - } - Expr::Value(val) => { - if let Some(ts_field_type) = translate_value(&val.value) { - Some((ts_field_type, false)) - } else { - Some((TsFieldType::Any, false)) - } - } - _ => Some((TsFieldType::Any, false)), - }; - - if let Some((value_type, is_nullable)) = value_type { - object_fields.push((key_name, value_type, is_nullable)); - } else { - // If we can't infer the type, return Any - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - } else { - // If we can't extract the value expression, return Any - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - } - - // Build the StructuredObject type with the inferred fields - let object_type = TsFieldType::StructuredObject(object_fields); - return ts_query.insert_result(Some(alias), &[object_type], is_selection, false, expr_for_logging); - } - - // For build_array functions, we'd need different logic - // For now, return Any - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + return handle_json_build_function( + function_name_str, + args, + single_table_name, + table_with_joins, + db_conn, + alias, + ts_query, + is_selection, + Some(expr_for_logging), + ) + .await; } // Handle JSON aggregation functions (jsonb_agg, json_agg, etc.) if is_json_agg_function(function_name_str) { - use sqlparser::ast::{FunctionArg, FunctionArgExpr, FunctionArguments}; + use sqlparser::ast::FunctionArguments; let args = match &func_obj.args { FunctionArguments::List(arg_list) => &arg_list.args, @@ -848,148 +682,17 @@ pub async fn translate_expr( } }; - // jsonb_agg typically takes a single expression - // e.g., jsonb_agg(jsonb_build_object('id', id, 'name', name)) - if args.len() == 1 { - let first_arg = &args[0]; - let arg_expr = match first_arg { - FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), - FunctionArg::Named { - arg: FunctionArgExpr::Expr(expr), - .. - } => Some(expr), - _ => None, - }; - - if let Some(arg_expr) = arg_expr { - // Check if the argument is a jsonb_build_object function - if let Expr::Function(inner_func) = arg_expr { - let inner_func_name = inner_func.name.to_string(); - if is_json_build_function(inner_func_name.as_str()) { - // Recursively infer the type of jsonb_build_object - let inner_args = match &inner_func.args { - FunctionArguments::List(arg_list) => &arg_list.args, - _ => { - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - }; - - if inner_args.len() % 2 != 0 { - // Invalid number of arguments - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - - let mut object_fields = vec![]; - - // Process key-value pairs - for i in (0..inner_args.len()).step_by(2) { - let key_arg = &inner_args[i]; - let value_arg = &inner_args[i + 1]; - - // Extract key name - let key_name = match key_arg { - FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(val))) => { - match &val.value { - Value::SingleQuotedString(s) | Value::DoubleQuotedString(s) => Some(s.clone()), - _ => None, - } - } - _ => None, - }; - - if key_name.is_none() { - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - - let key_name = key_name.unwrap(); - - // Extract value type - let value_expr = match value_arg { - FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => Some(expr), - FunctionArg::Named { - arg: FunctionArgExpr::Expr(expr), - .. - } => Some(expr), - _ => None, - }; - - if let Some(value_expr) = value_expr { - let value_type = match value_expr { - Expr::Identifier(ident) => { - let column_name = DisplayIndent(ident).to_string(); - if let Some(table_name) = single_table_name { - let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], db_conn).await; - - if let Some(table_details) = table_details { - if let Some(field) = table_details.get(&column_name) { - Some((field.field_type.to_owned(), field.is_nullable)) - } else { - Some((TsFieldType::Any, false)) - } - } else { - Some((TsFieldType::Any, false)) - } - } else { - Some((TsFieldType::Any, false)) - } - } - Expr::CompoundIdentifier(idents) if idents.len() == 2 => { - let column_name = DisplayIndent(&idents[1]).to_string(); - if let Ok(table_name) = translate_table_from_expr(table_with_joins, value_expr) { - let table_details = &DB_SCHEMA - .lock() - .await - .fetch_table(&vec![table_name.as_str()], db_conn) - .await; - - if let Some(table_details) = table_details { - if let Some(field) = table_details.get(&column_name) { - Some((field.field_type.to_owned(), field.is_nullable)) - } else { - Some((TsFieldType::Any, false)) - } - } else { - Some((TsFieldType::Any, false)) - } - } else { - Some((TsFieldType::Any, false)) - } - } - Expr::Value(val) => { - if let Some(ts_field_type) = translate_value(&val.value) { - Some((ts_field_type, false)) - } else { - Some((TsFieldType::Any, false)) - } - } - _ => Some((TsFieldType::Any, false)), - }; - - if let Some((value_type, is_nullable)) = value_type { - object_fields.push((key_name, value_type, is_nullable)); - } else { - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - } else { - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - } - - // Build the Array of StructuredObject type - let object_type = TsFieldType::StructuredObject(object_fields); - let array_type = TsFieldType::Array(Box::new(object_type)); - return ts_query.insert_result(Some(alias), &[array_type], is_selection, false, expr_for_logging); - } - } - - // If not jsonb_build_object, try to infer the type of the expression - // For now, return Any - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); - } - } - - // If we can't infer the type, return Any - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging); + return handle_json_agg_function( + args, + single_table_name, + table_with_joins, + db_conn, + alias, + ts_query, + is_selection, + Some(expr_for_logging), + ) + .await; } // Handle other function types From 4e9141ab62bc00cc9fadbbc53a08ec2ea8d9959e Mon Sep 17 00:00:00 2001 From: JasonShin Date: Fri, 6 Mar 2026 23:07:15 +1100 Subject: [PATCH 04/19] fmt --- .../expressions/function_handlers/json_functions.rs | 6 ++++-- .../expressions/function_handlers/polymorphic_functions.rs | 2 +- src/ts_generator/sql_parser/expressions/functions.rs | 7 +------ src/ts_generator/sql_parser/expressions/translate_expr.rs | 4 +++- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs index fed13556..d198bf9b 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs @@ -115,7 +115,8 @@ pub async fn process_json_build_object_args( let value_expr = extract_expr_from_arg(value_arg)?; // Infer value type - let (value_type, is_nullable) = infer_type_from_expr(value_expr, single_table_name, table_with_joins, db_conn).await?; + let (value_type, is_nullable) = + infer_type_from_expr(value_expr, single_table_name, table_with_joins, db_conn).await?; object_fields.push((key_name, value_type, is_nullable)); } @@ -139,7 +140,8 @@ pub async fn handle_json_build_function( // Handle jsonb_build_object / json_build_object if function_name.to_uppercase() == "JSONB_BUILD_OBJECT" || function_name.to_uppercase() == "JSON_BUILD_OBJECT" { - if let Some(object_fields) = process_json_build_object_args(args, single_table_name, table_with_joins, db_conn).await + if let Some(object_fields) = + process_json_build_object_args(args, single_table_name, table_with_joins, db_conn).await { let object_type = TsFieldType::StructuredObject(object_fields); return ts_query.insert_result(Some(alias), &[object_type], is_selection, false, expr_log); diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs index 198c5d0c..14efa6cc 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs @@ -1,10 +1,10 @@ -use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments, TableWithJoins}; use crate::common::lazy::DB_SCHEMA; use crate::ts_generator::errors::TsGeneratorError; use crate::ts_generator::sql_parser::expressions::translate_data_type::translate_value; use crate::ts_generator::sql_parser::expressions::translate_table_with_joins::translate_table_from_expr; use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; use crate::ts_generator::types::ts_query::{TsFieldType, TsQuery}; +use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments, TableWithJoins}; pub async fn handle_polymorphic_functions( ts_query: &mut TsQuery, diff --git a/src/ts_generator/sql_parser/expressions/functions.rs b/src/ts_generator/sql_parser/expressions/functions.rs index 51f2397d..60d9bb6c 100644 --- a/src/ts_generator/sql_parser/expressions/functions.rs +++ b/src/ts_generator/sql_parser/expressions/functions.rs @@ -180,12 +180,7 @@ pub static JSON_BUILD_FUNCTIONS: &[&str] = &[ ]; // JSON/JSONB aggregation functions -pub static JSON_AGG_FUNCTIONS: &[&str] = &[ - "JSONB_AGG", - "JSON_AGG", - "JSON_OBJECT_AGG", - "JSONB_OBJECT_AGG", -]; +pub static JSON_AGG_FUNCTIONS: &[&str] = &["JSONB_AGG", "JSON_AGG", "JSON_OBJECT_AGG", "JSONB_OBJECT_AGG"]; pub fn is_json_build_function(func_name: &str) -> bool { JSON_BUILD_FUNCTIONS.contains(&func_name.to_uppercase().as_str()) diff --git a/src/ts_generator/sql_parser/expressions/translate_expr.rs b/src/ts_generator/sql_parser/expressions/translate_expr.rs index 6821ba1e..b3d6bd92 100644 --- a/src/ts_generator/sql_parser/expressions/translate_expr.rs +++ b/src/ts_generator/sql_parser/expressions/translate_expr.rs @@ -1,6 +1,8 @@ use super::function_handlers::json_functions::{handle_json_agg_function, handle_json_build_function}; use super::function_handlers::polymorphic_functions::handle_polymorphic_functions; -use super::functions::{is_date_function, is_json_agg_function, is_json_build_function, is_numeric_function, is_type_polymorphic_function}; +use super::functions::{ + is_date_function, is_json_agg_function, is_json_build_function, is_numeric_function, is_type_polymorphic_function, +}; use crate::common::lazy::DB_SCHEMA; use crate::common::logger::{error, warning}; use crate::core::connection::DBConn; From 23f272c7f9cf32426128bf1ccfc4adab19e3ab27 Mon Sep 17 00:00:00 2001 From: JasonShin Date: Fri, 6 Mar 2026 23:15:20 +1100 Subject: [PATCH 05/19] organise --- .../function_handlers/json_functions.rs | 85 +++++++++---------- .../expressions/function_handlers/mod.rs | 15 ++++ .../polymorphic_functions.rs | 42 +++++---- .../sql_parser/expressions/translate_expr.rs | 38 ++++----- tests/sample/sample.queries.ts | 44 ++++++++++ 5 files changed, 139 insertions(+), 85 deletions(-) create mode 100644 tests/sample/sample.queries.ts diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs index d198bf9b..9dcc0746 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs @@ -4,7 +4,8 @@ use crate::ts_generator::errors::TsGeneratorError; use crate::ts_generator::sql_parser::expressions::translate_data_type::translate_value; use crate::ts_generator::sql_parser::expressions::translate_table_with_joins::translate_table_from_expr; use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; -use crate::ts_generator::types::ts_query::{TsFieldType, TsQuery}; +use crate::ts_generator::types::ts_query::{TsFieldType}; +use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; use sqlparser::ast::{Expr, FunctionArg, FunctionArgExpr, TableWithJoins, Value}; /// Extract key name from a function argument (should be a string literal) @@ -96,7 +97,7 @@ pub async fn process_json_build_object_args( table_with_joins: &Option>, db_conn: &DBConn, ) -> Option> { - if args.len() % 2 != 0 { + if !args.len().is_multiple_of(2) { // Invalid number of arguments return None; } @@ -128,78 +129,76 @@ pub async fn process_json_build_object_args( pub async fn handle_json_build_function( function_name: &str, args: &[FunctionArg], - single_table_name: &Option<&str>, - table_with_joins: &Option>, - db_conn: &DBConn, - alias: &str, - ts_query: &mut TsQuery, - is_selection: bool, - expr_for_logging: Option<&str>, + ctx: &mut FunctionHandlersContext<'_>, ) -> Result<(), TsGeneratorError> { - let expr_log = expr_for_logging.unwrap_or(""); + let expr_log = ctx.expr_for_logging.unwrap_or(""); // Handle jsonb_build_object / json_build_object if function_name.to_uppercase() == "JSONB_BUILD_OBJECT" || function_name.to_uppercase() == "JSON_BUILD_OBJECT" { if let Some(object_fields) = - process_json_build_object_args(args, single_table_name, table_with_joins, db_conn).await + process_json_build_object_args(args, ctx.single_table_name, ctx.table_with_joins, ctx.db_conn).await { let object_type = TsFieldType::StructuredObject(object_fields); - return ts_query.insert_result(Some(alias), &[object_type], is_selection, false, expr_log); + return ctx + .ts_query + .insert_result(Some(ctx.alias), &[object_type], ctx.is_selection, false, expr_log); } } // For other build functions or on failure, return Any - ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_log) + ctx + .ts_query + .insert_result(Some(ctx.alias), &[TsFieldType::Any], ctx.is_selection, false, expr_log) } /// Handle JSON aggregation functions (jsonb_agg, json_agg, etc.) pub async fn handle_json_agg_function( args: &[FunctionArg], - single_table_name: &Option<&str>, - table_with_joins: &Option>, - db_conn: &DBConn, - alias: &str, - ts_query: &mut TsQuery, - is_selection: bool, - expr_for_logging: Option<&str>, + ctx: &mut FunctionHandlersContext<'_>, ) -> Result<(), TsGeneratorError> { use super::super::functions::is_json_build_function; use sqlparser::ast::FunctionArguments; - let expr_log = expr_for_logging.unwrap_or(""); + let expr_log = ctx.expr_for_logging.unwrap_or(""); // jsonb_agg typically takes a single expression if args.len() != 1 { - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_log); + return ctx + .ts_query + .insert_result(Some(ctx.alias), &[TsFieldType::Any], ctx.is_selection, false, expr_log); } let arg_expr = extract_expr_from_arg(&args[0]); - if let Some(arg_expr) = arg_expr { - // Check if the argument is a jsonb_build_object function - if let Expr::Function(inner_func) = arg_expr { - let inner_func_name = inner_func.name.to_string(); - if is_json_build_function(inner_func_name.as_str()) { - // Extract arguments from the inner function - let inner_args = match &inner_func.args { - FunctionArguments::List(arg_list) => &arg_list.args, - _ => { - return ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_log); - } - }; - - // Process the inner jsonb_build_object - if let Some(object_fields) = - process_json_build_object_args(inner_args, single_table_name, table_with_joins, db_conn).await - { - let object_type = TsFieldType::StructuredObject(object_fields); - let array_type = TsFieldType::Array(Box::new(object_type)); - return ts_query.insert_result(Some(alias), &[array_type], is_selection, false, expr_log); + // Check if the argument is a jsonb_build_object function + if let Some(Expr::Function(inner_func)) = arg_expr { + let inner_func_name = inner_func.name.to_string(); + if is_json_build_function(inner_func_name.as_str()) { + // Extract arguments from the inner function + let inner_args = match &inner_func.args { + FunctionArguments::List(arg_list) => &arg_list.args, + _ => { + return ctx + .ts_query + .insert_result(Some(ctx.alias), &[TsFieldType::Any], ctx.is_selection, false, expr_log); } + }; + + // Process the inner jsonb_build_object + if let Some(object_fields) = + process_json_build_object_args(inner_args, ctx.single_table_name, ctx.table_with_joins, ctx.db_conn).await + { + let object_type = TsFieldType::StructuredObject(object_fields); + let array_type = TsFieldType::Array(Box::new(object_type)); + return ctx + .ts_query + .insert_result(Some(ctx.alias), &[array_type], ctx.is_selection, false, expr_log); } } } // If we can't infer the type, return Any - ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_log) + ctx + .ts_query + .insert_result(Some(ctx.alias), &[TsFieldType::Any], ctx.is_selection, false, expr_log) } diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs b/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs index 988b9f41..224d7183 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs @@ -1,2 +1,17 @@ +use sqlparser::ast::TableWithJoins; +use crate::core::connection::DBConn; +use crate::ts_generator::types::ts_query::TsQuery; + pub mod json_functions; pub mod polymorphic_functions; + +/// Context for function type inference +pub struct FunctionHandlersContext<'a> { + pub ts_query: &'a mut TsQuery, + pub single_table_name: &'a Option<&'a str>, + pub table_with_joins: &'a Option>, + pub db_conn: &'a DBConn, + pub alias: &'a str, + pub is_selection: bool, + pub expr_for_logging: Option<&'a str>, +} \ No newline at end of file diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs index 14efa6cc..e129e1ac 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs @@ -3,19 +3,15 @@ use crate::ts_generator::errors::TsGeneratorError; use crate::ts_generator::sql_parser::expressions::translate_data_type::translate_value; use crate::ts_generator::sql_parser::expressions::translate_table_with_joins::translate_table_from_expr; use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; -use crate::ts_generator::types::ts_query::{TsFieldType, TsQuery}; -use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments, TableWithJoins}; +use crate::ts_generator::types::ts_query::TsFieldType; +use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; +use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments}; pub async fn handle_polymorphic_functions( - ts_query: &mut TsQuery, - single_table_name: &Option<&str>, - table_with_joins: &Option>, - alias: &str, - is_selection: bool, - expr_for_logging: &str, func_obj: &Function, - db_conn: &crate::core::connection::DBConn, + ctx: &mut FunctionHandlersContext<'_>, ) -> Result<(), TsGeneratorError> { + let expr_log = ctx.expr_for_logging.unwrap_or(""); // In sqlparser 0.59.0, args is a FunctionArguments enum // Extract the first argument from the appropriate variant let first_arg = match &func_obj.args { @@ -39,17 +35,17 @@ pub async fn handle_polymorphic_functions( match arg_expr { Expr::Identifier(ident) => { let column_name = DisplayIndent(ident).to_string(); - if let Some(table_name) = single_table_name { - let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], db_conn).await; + if let Some(table_name) = ctx.single_table_name { + let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], ctx.db_conn).await; if let Some(table_details) = table_details { if let Some(field) = table_details.get(&column_name) { - return ts_query.insert_result( - Some(alias), + return ctx.ts_query.insert_result( + Some(ctx.alias), &[field.field_type.to_owned()], - is_selection, + ctx.is_selection, false, // IFNULL/COALESCE removes nullability - expr_for_logging, + expr_log, ); } } @@ -57,21 +53,21 @@ pub async fn handle_polymorphic_functions( } Expr::CompoundIdentifier(idents) if idents.len() == 2 => { let column_name = DisplayIndent(&idents[1]).to_string(); - if let Ok(table_name) = translate_table_from_expr(table_with_joins, arg_expr) { + if let Ok(table_name) = translate_table_from_expr(ctx.table_with_joins, arg_expr) { let table_details = &DB_SCHEMA .lock() .await - .fetch_table(&vec![table_name.as_str()], db_conn) + .fetch_table(&vec![table_name.as_str()], ctx.db_conn) .await; if let Some(table_details) = table_details { if let Some(field) = table_details.get(&column_name) { - return ts_query.insert_result( - Some(alias), + return ctx.ts_query.insert_result( + Some(ctx.alias), &[field.field_type.to_owned()], - is_selection, + ctx.is_selection, false, // IFNULL/COALESCE removes nullability - expr_for_logging, + expr_log, ); } } @@ -80,7 +76,7 @@ pub async fn handle_polymorphic_functions( Expr::Value(val) => { // If first arg is a literal value, infer from that if let Some(ts_field_type) = translate_value(&val.value) { - return ts_query.insert_result(Some(alias), &[ts_field_type], is_selection, false, expr_for_logging); + return ctx.ts_query.insert_result(Some(ctx.alias), &[ts_field_type], ctx.is_selection, false, expr_log); } } _ => {} @@ -89,5 +85,5 @@ pub async fn handle_polymorphic_functions( } // Fallback to Any if we couldn't infer the type - ts_query.insert_result(Some(alias), &[TsFieldType::Any], is_selection, false, expr_for_logging) + ctx.ts_query.insert_result(Some(ctx.alias), &[TsFieldType::Any], ctx.is_selection, false, expr_log) } diff --git a/src/ts_generator/sql_parser/expressions/translate_expr.rs b/src/ts_generator/sql_parser/expressions/translate_expr.rs index b3d6bd92..f128f611 100644 --- a/src/ts_generator/sql_parser/expressions/translate_expr.rs +++ b/src/ts_generator/sql_parser/expressions/translate_expr.rs @@ -14,6 +14,7 @@ use crate::ts_generator::sql_parser::expressions::{ }; use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; use crate::ts_generator::sql_parser::translate_query::translate_query; +use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; use crate::ts_generator::types::ts_query::{TsFieldType, TsQuery}; use async_recursion::async_recursion; use color_eyre::Result; @@ -633,17 +634,17 @@ pub async fn translate_expr( // Handle type-polymorphic functions (IFNULL, COALESCE, etc.) // These functions return the type of their first argument if is_type_polymorphic_function(function_name_str) { - return handle_polymorphic_functions( + let mut ctx = FunctionHandlersContext { ts_query, single_table_name, table_with_joins, + db_conn, alias, is_selection, - expr_for_logging, - func_obj, - db_conn, - ) - .await; + expr_for_logging: Some(expr_for_logging), + }; + + return handle_polymorphic_functions(func_obj, &mut ctx).await; } // Handle JSON build functions (jsonb_build_object, json_build_object, etc.) @@ -658,18 +659,17 @@ pub async fn translate_expr( } }; - return handle_json_build_function( - function_name_str, - args, + let mut ctx = FunctionHandlersContext { + ts_query, single_table_name, table_with_joins, db_conn, alias, - ts_query, is_selection, - Some(expr_for_logging), - ) - .await; + expr_for_logging: Some(expr_for_logging), + }; + + return handle_json_build_function(function_name_str, args, &mut ctx).await; } // Handle JSON aggregation functions (jsonb_agg, json_agg, etc.) @@ -684,17 +684,17 @@ pub async fn translate_expr( } }; - return handle_json_agg_function( - args, + let mut ctx = FunctionHandlersContext { + ts_query, single_table_name, table_with_joins, db_conn, alias, - ts_query, is_selection, - Some(expr_for_logging), - ) - .await; + expr_for_logging: Some(expr_for_logging), + }; + + return handle_json_agg_function(args, &mut ctx).await; } // Handle other function types diff --git a/tests/sample/sample.queries.ts b/tests/sample/sample.queries.ts new file mode 100644 index 00000000..f29c73b5 --- /dev/null +++ b/tests/sample/sample.queries.ts @@ -0,0 +1,44 @@ +export type SampleSelectQueryParams = [number]; + +export interface ISampleSelectQueryResult { + name: string; + some_id: number; +} + +export interface ISampleSelectQueryQuery { + params: SampleSelectQueryParams; + result: ISampleSelectQueryResult; +} + +export type SampleInsertQueryParams = [string]; + +export interface ISampleInsertQueryResult { + +} + +export interface ISampleInsertQueryQuery { + params: SampleInsertQueryParams; + result: ISampleInsertQueryResult; +} + +export type SampleUpdateQueryParams = [string, number]; + +export interface ISampleUpdateQueryResult { + +} + +export interface ISampleUpdateQueryQuery { + params: SampleUpdateQueryParams; + result: ISampleUpdateQueryResult; +} + +export type SampleDeleteQueryParams = [number]; + +export interface ISampleDeleteQueryResult { + +} + +export interface ISampleDeleteQueryQuery { + params: SampleDeleteQueryParams; + result: ISampleDeleteQueryResult; +} From 76b5b09a5aa7b95502a257f330ea347b9dbb348b Mon Sep 17 00:00:00 2001 From: JasonShin Date: Fri, 6 Mar 2026 23:18:21 +1100 Subject: [PATCH 06/19] fix --- .sqlxrc.sample.json | 3 +- playpen/db/mysql_migration_5_6.sql | 52 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.sqlxrc.sample.json b/.sqlxrc.sample.json index a763b08e..ee7909cf 100644 --- a/.sqlxrc.sample.json +++ b/.sqlxrc.sample.json @@ -11,7 +11,8 @@ "DB_PORT": 54321, "DB_USER": "postgres", "DB_PASS": "postgres", - "DB_NAME": "postgres" + "DB_NAME": "postgres", + "PG_SEARCH_PATH": "public,myschema" }, "db_mysql": { "DB_TYPE": "mysql", diff --git a/playpen/db/mysql_migration_5_6.sql b/playpen/db/mysql_migration_5_6.sql index ef5cc6c1..4040c66a 100644 --- a/playpen/db/mysql_migration_5_6.sql +++ b/playpen/db/mysql_migration_5_6.sql @@ -158,3 +158,55 @@ CREATE TABLE random ( json1 TEXT ); + + +-- JSON Test Data Table +-- This table contains various JSON structures for testing JSON operators and functions +CREATE TABLE json_test_data ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + data TEXT NOT NULL, + metadata TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +INSERT INTO json_test_data (name, data, metadata) VALUES +-- Simple object +('user_profile', + '{"userId": 1, "username": "john_doe", "email": "john@example.com", "age": 30, "active": true}', + '{"source": "api", "version": "1.0"}'), + +-- Nested object with address +('user_with_address', + '{"userId": 2, "username": "jane_smith", "email": "jane@example.com", "address": {"street": "123 Main St", "city": "Springfield", "state": "IL", "zipCode": "62701", "country": "USA"}}', + '{"source": "import", "version": "1.0"}'), + +-- Array of items +('shopping_cart', + '{"cartId": 101, "items": [{"productId": 1, "name": "Laptop", "quantity": 1, "price": 999.99}, {"productId": 2, "name": "Mouse", "quantity": 2, "price": 25.50}], "totalPrice": 1050.99}', + '{"source": "web", "version": "2.0"}'), + +-- Array of strings +('tags', + '{"postId": 42, "title": "MySQL JSON Functions", "tags": ["database", "mysql", "json", "tutorial"], "published": true}', + '{"source": "cms", "version": "1.0"}'), + +-- Nested arrays and objects +('game_stats', + '{"playerId": 123, "stats": {"level": 50, "experience": 125000, "inventory": [{"slot": 1, "item": "Sword of Fire", "rarity": "legendary"}, {"slot": 2, "item": "Shield of Light", "rarity": "epic"}], "achievements": ["First Kill", "Level 50", "Legendary Item"]}}', + '{"source": "game_server", "version": "3.0"}'), + +-- Deep nesting +('nested_config', + '{"app": {"name": "MyApp", "version": "1.0.0", "settings": {"database": {"host": "localhost", "port": 3306, "credentials": {"username": "admin", "encrypted": true}}, "features": {"darkMode": true, "notifications": {"email": true, "push": false}}}}}', + '{"source": "config", "version": "1.0"}'), + +-- Array of objects with nulls +('product_reviews', + '{"productId": 456, "reviews": [{"reviewId": 1, "rating": 5, "comment": "Excellent product!", "reviewer": "Alice"}, {"reviewId": 2, "rating": 4, "comment": null, "reviewer": "Bob"}, {"reviewId": 3, "rating": 3, "comment": "Average", "reviewer": null}]}', + '{"source": "reviews", "version": "1.0"}'), + +-- Mixed types +('analytics', + '{"date": "2024-01-15", "metrics": {"visitors": 1500, "pageViews": 4500, "bounceRate": 0.35, "sources": {"organic": 850, "direct": 400, "referral": 250}}}', + '{"source": "analytics", "version": "1.0"}'); From 5d36a9a1b0d38954ae3390115c9e204a1499d86d Mon Sep 17 00:00:00 2001 From: JasonShin Date: Fri, 6 Mar 2026 23:19:22 +1100 Subject: [PATCH 07/19] cleanup --- tests/demo/mysql/json_operations.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/demo/mysql/json_operations.ts b/tests/demo/mysql/json_operations.ts index 36b84a33..1fd1625f 100644 --- a/tests/demo/mysql/json_operations.ts +++ b/tests/demo/mysql/json_operations.ts @@ -3,7 +3,6 @@ import { sql } from 'sqlx-ts' // JSON_OBJECT basic - build object from columns const jsonObjectBasic = sql` -- @db: db_mysql --- @db: db_mysql -- @name: json object basic SELECT items.id AS id, @@ -14,7 +13,6 @@ FROM items // JSON_ARRAYAGG for aggregation - aggregate rows into JSON array const jsonArrayAggregation = sql` -- @db: db_mysql --- @db: db_mysql -- @name: json array aggregation SELECT items.rarity AS rarity, @@ -26,7 +24,6 @@ GROUP BY items.rarity // JSON operators in SELECT - extract values const jsonOperatorsSelect = sql` -- @db: db_mysql --- @db: db_mysql -- @name: json operators select SELECT json_test_data.id AS id, From 9a67dba97ea93fa1164c527cb8ec42b38d2c54e8 Mon Sep 17 00:00:00 2001 From: JasonShin Date: Fri, 6 Mar 2026 23:20:30 +1100 Subject: [PATCH 08/19] fmt --- .../function_handlers/json_functions.rs | 4 ++-- .../expressions/function_handlers/mod.rs | 4 ++-- .../function_handlers/polymorphic_functions.rs | 16 ++++++++++++---- .../sql_parser/expressions/translate_expr.rs | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs index 9dcc0746..b55b6c3f 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs @@ -1,11 +1,11 @@ use crate::common::lazy::DB_SCHEMA; use crate::core::connection::DBConn; use crate::ts_generator::errors::TsGeneratorError; +use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; use crate::ts_generator::sql_parser::expressions::translate_data_type::translate_value; use crate::ts_generator::sql_parser::expressions::translate_table_with_joins::translate_table_from_expr; use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; -use crate::ts_generator::types::ts_query::{TsFieldType}; -use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; +use crate::ts_generator::types::ts_query::TsFieldType; use sqlparser::ast::{Expr, FunctionArg, FunctionArgExpr, TableWithJoins, Value}; /// Extract key name from a function argument (should be a string literal) diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs b/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs index 224d7183..2796df6d 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/mod.rs @@ -1,6 +1,6 @@ -use sqlparser::ast::TableWithJoins; use crate::core::connection::DBConn; use crate::ts_generator::types::ts_query::TsQuery; +use sqlparser::ast::TableWithJoins; pub mod json_functions; pub mod polymorphic_functions; @@ -14,4 +14,4 @@ pub struct FunctionHandlersContext<'a> { pub alias: &'a str, pub is_selection: bool, pub expr_for_logging: Option<&'a str>, -} \ No newline at end of file +} diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs index e129e1ac..e32d04f4 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/polymorphic_functions.rs @@ -1,10 +1,10 @@ use crate::common::lazy::DB_SCHEMA; use crate::ts_generator::errors::TsGeneratorError; +use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; use crate::ts_generator::sql_parser::expressions::translate_data_type::translate_value; use crate::ts_generator::sql_parser::expressions::translate_table_with_joins::translate_table_from_expr; use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; use crate::ts_generator::types::ts_query::TsFieldType; -use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments}; pub async fn handle_polymorphic_functions( @@ -36,7 +36,11 @@ pub async fn handle_polymorphic_functions( Expr::Identifier(ident) => { let column_name = DisplayIndent(ident).to_string(); if let Some(table_name) = ctx.single_table_name { - let table_details = &DB_SCHEMA.lock().await.fetch_table(&vec![table_name], ctx.db_conn).await; + let table_details = &DB_SCHEMA + .lock() + .await + .fetch_table(&vec![table_name], ctx.db_conn) + .await; if let Some(table_details) = table_details { if let Some(field) = table_details.get(&column_name) { @@ -76,7 +80,9 @@ pub async fn handle_polymorphic_functions( Expr::Value(val) => { // If first arg is a literal value, infer from that if let Some(ts_field_type) = translate_value(&val.value) { - return ctx.ts_query.insert_result(Some(ctx.alias), &[ts_field_type], ctx.is_selection, false, expr_log); + return ctx + .ts_query + .insert_result(Some(ctx.alias), &[ts_field_type], ctx.is_selection, false, expr_log); } } _ => {} @@ -85,5 +91,7 @@ pub async fn handle_polymorphic_functions( } // Fallback to Any if we couldn't infer the type - ctx.ts_query.insert_result(Some(ctx.alias), &[TsFieldType::Any], ctx.is_selection, false, expr_log) + ctx + .ts_query + .insert_result(Some(ctx.alias), &[TsFieldType::Any], ctx.is_selection, false, expr_log) } diff --git a/src/ts_generator/sql_parser/expressions/translate_expr.rs b/src/ts_generator/sql_parser/expressions/translate_expr.rs index f128f611..1e076fb3 100644 --- a/src/ts_generator/sql_parser/expressions/translate_expr.rs +++ b/src/ts_generator/sql_parser/expressions/translate_expr.rs @@ -7,6 +7,7 @@ use crate::common::lazy::DB_SCHEMA; use crate::common::logger::{error, warning}; use crate::core::connection::DBConn; use crate::ts_generator::errors::TsGeneratorError; +use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; use crate::ts_generator::sql_parser::expressions::translate_data_type::translate_value; use crate::ts_generator::sql_parser::expressions::translate_table_with_joins::translate_table_from_expr; use crate::ts_generator::sql_parser::expressions::{ @@ -14,7 +15,6 @@ use crate::ts_generator::sql_parser::expressions::{ }; use crate::ts_generator::sql_parser::quoted_strings::DisplayIndent; use crate::ts_generator::sql_parser::translate_query::translate_query; -use crate::ts_generator::sql_parser::expressions::function_handlers::FunctionHandlersContext; use crate::ts_generator::types::ts_query::{TsFieldType, TsQuery}; use async_recursion::async_recursion; use color_eyre::Result; From 289c8ce596611ac1b735bf4beb4fa59bd45d3fea Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 14:57:35 +1100 Subject: [PATCH 09/19] fix tests --- docker-compose.yml | 4 +- .../mysql/json_access_operators.snapshot.ts | 1 + .../mysql/json_array_functions.snapshot.ts | 1 + .../demo/mysql/json_comprehensive.snapshot.ts | 1 + .../mysql/json_object_functions.snapshot.ts | 13 ++--- tests/demo/mysql/json_operations.snapshot.ts | 1 + .../json_access_operators.snapshot.ts | 53 ++++++++++--------- .../postgres/json_array_functions.snapshot.ts | 1 + .../postgres/json_comprehensive.snapshot.ts | 29 +++++----- 9 files changed, 57 insertions(+), 47 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 99f07597..77e78534 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ version: '3.1' services: postgres: + platform: linux/amd64 image: postgres:${PG_VERSION:-13} restart: always environment: @@ -13,7 +14,8 @@ services: - 54321:5432 mysql: - image: mysql:${MYSQL_VERSION:-8} + platform: linux/amd64 + image: mysql:5.7 restart: always volumes: - ./playpen/db/${MYSQL_MIGRATION_FILE:-mysql_migration.sql}:/docker-entrypoint-initdb.d/mysql_migration.sql diff --git a/tests/demo/mysql/json_access_operators.snapshot.ts b/tests/demo/mysql/json_access_operators.snapshot.ts index 81a2595e..c93b1dc7 100644 --- a/tests/demo/mysql/json_access_operators.snapshot.ts +++ b/tests/demo/mysql/json_access_operators.snapshot.ts @@ -126,3 +126,4 @@ export interface IJsonNullHandlingQuery { params: JsonNullHandlingParams; result: IJsonNullHandlingResult; } + diff --git a/tests/demo/mysql/json_array_functions.snapshot.ts b/tests/demo/mysql/json_array_functions.snapshot.ts index 6b057d4f..1b069362 100644 --- a/tests/demo/mysql/json_array_functions.snapshot.ts +++ b/tests/demo/mysql/json_array_functions.snapshot.ts @@ -128,3 +128,4 @@ export interface IJsonArrayInsertQuery { params: JsonArrayInsertParams; result: IJsonArrayInsertResult; } + diff --git a/tests/demo/mysql/json_comprehensive.snapshot.ts b/tests/demo/mysql/json_comprehensive.snapshot.ts index 1067842b..af43c197 100644 --- a/tests/demo/mysql/json_comprehensive.snapshot.ts +++ b/tests/demo/mysql/json_comprehensive.snapshot.ts @@ -173,3 +173,4 @@ export interface IJsonSearchQuery { params: JsonSearchParams; result: IJsonSearchResult; } + diff --git a/tests/demo/mysql/json_object_functions.snapshot.ts b/tests/demo/mysql/json_object_functions.snapshot.ts index dadda716..35cb4bdf 100644 --- a/tests/demo/mysql/json_object_functions.snapshot.ts +++ b/tests/demo/mysql/json_object_functions.snapshot.ts @@ -89,7 +89,7 @@ export type JsonSetParams = []; export interface IJsonSetResult { id: number; name: string; - originalData: any; + originalData: object; updatedAge: any; updatedCity: any; } @@ -104,7 +104,7 @@ export type JsonInsertParams = []; export interface IJsonInsertResult { id: number; name: string; - originalData: any; + originalData: object; withPhone: any; } @@ -118,7 +118,7 @@ export type JsonReplaceParams = []; export interface IJsonReplaceResult { id: number; name: string; - originalData: any; + originalData: object; withNewUsername: any; } @@ -132,7 +132,7 @@ export type JsonRemoveParams = []; export interface IJsonRemoveResult { id: number; name: string; - originalData: any; + originalData: object; withoutAge: any; } @@ -147,7 +147,7 @@ export interface IJsonMergePatchResult { id: number; mergedData: any; name: string; - originalData: any; + originalData: object; } export interface IJsonMergePatchQuery { @@ -161,7 +161,7 @@ export interface IJsonMergePreserveResult { id: number; mergedData: any; name: string; - originalData: any; + originalData: object; } export interface IJsonMergePreserveQuery { @@ -208,3 +208,4 @@ export interface IJsonValidQuery { params: JsonValidParams; result: IJsonValidResult; } + diff --git a/tests/demo/mysql/json_operations.snapshot.ts b/tests/demo/mysql/json_operations.snapshot.ts index ec3e5c7d..c78e66b3 100644 --- a/tests/demo/mysql/json_operations.snapshot.ts +++ b/tests/demo/mysql/json_operations.snapshot.ts @@ -34,3 +34,4 @@ export interface IJsonOperatorsSelectQuery { params: JsonOperatorsSelectParams; result: IJsonOperatorsSelectResult; } + diff --git a/tests/demo/postgres/json_access_operators.snapshot.ts b/tests/demo/postgres/json_access_operators.snapshot.ts index eb972c33..5897fcfb 100644 --- a/tests/demo/postgres/json_access_operators.snapshot.ts +++ b/tests/demo/postgres/json_access_operators.snapshot.ts @@ -1,11 +1,11 @@ export type JsonFieldAccessParams = []; export interface IJsonFieldAccessResult { - active_json: string; - age_json: string; + activeJson: string; + ageJson: string; id: number; name: string; - username_json: string; + usernameJson: string; } export interface IJsonFieldAccessQuery { @@ -32,14 +32,14 @@ export interface IJsonFieldAccessTextQuery { export type JsonNestedAccessParams = []; export interface IJsonNestedAccessResult { - address_json: string; + addressJson: string; city: string; - city_json: string; + cityJson: string; id: number; name: string; street: string; - street_json: string; - zip_code: string; + streetJson: string; + zipCode: string; } export interface IJsonNestedAccessQuery { @@ -50,13 +50,13 @@ export interface IJsonNestedAccessQuery { export type JsonArrayAccessParams = []; export interface IJsonArrayAccessResult { - first_item_json: number; - first_item_name: string; - first_item_price: string; + firstItemJson: number; + firstItemName: string; + firstItemPrice: string; id: number; - items_json: string; + itemsJson: string; name: string; - second_item_json: number; + secondItemJson: number; } export interface IJsonArrayAccessQuery { @@ -67,12 +67,12 @@ export interface IJsonArrayAccessQuery { export type JsonPathAccessParams = []; export interface IJsonPathAccessResult { - first_item_json: string; - first_item_name: string; - first_item_rarity: string; + firstItemJson: string; + firstItemName: string; + firstItemRarity: string; id: number; level: string; - level_json: string; + levelJson: string; name: string; } @@ -84,11 +84,11 @@ export interface IJsonPathAccessQuery { export type JsonDeepPathAccessParams = []; export interface IJsonDeepPathAccessResult { - dark_mode: string; - db_host: string; - db_host_json: string; - db_port: string; - email_notifications: string; + darkMode: string; + dbHost: string; + dbHostJson: string; + dbPort: string; + emailNotifications: string; id: number; name: string; } @@ -115,15 +115,16 @@ export interface IJsonFilterByFieldQuery { export type JsonNullHandlingParams = []; export interface IJsonNullHandlingResult { - first_comment: string; - first_reviewer: string; + firstComment: string; + firstReviewer: string; id: number; - second_comment: string; - third_comment: string; - third_reviewer: string; + secondComment: string; + thirdComment: string; + thirdReviewer: string; } export interface IJsonNullHandlingQuery { params: JsonNullHandlingParams; result: IJsonNullHandlingResult; } + diff --git a/tests/demo/postgres/json_array_functions.snapshot.ts b/tests/demo/postgres/json_array_functions.snapshot.ts index cd1e5fed..52fb189e 100644 --- a/tests/demo/postgres/json_array_functions.snapshot.ts +++ b/tests/demo/postgres/json_array_functions.snapshot.ts @@ -24,3 +24,4 @@ export interface IJsonbArrayContainsQuery { params: JsonbArrayContainsParams; result: IJsonbArrayContainsResult; } + diff --git a/tests/demo/postgres/json_comprehensive.snapshot.ts b/tests/demo/postgres/json_comprehensive.snapshot.ts index 90455fdf..1945a372 100644 --- a/tests/demo/postgres/json_comprehensive.snapshot.ts +++ b/tests/demo/postgres/json_comprehensive.snapshot.ts @@ -19,7 +19,7 @@ export interface IJsonNestedAccessResult { city: string; id: number; name: string; - zip_code: string; + zipCode: string; } export interface IJsonNestedAccessQuery { @@ -30,8 +30,8 @@ export interface IJsonNestedAccessQuery { export type JsonArrayIndexParams = []; export interface IJsonArrayIndexResult { - first_item_name: string; - first_item_price: number; + firstItemName: string; + firstItemPrice: number; id: number; name: string; } @@ -46,7 +46,7 @@ export type JsonArrayLengthParams = []; export interface IJsonArrayLengthResult { id: number; name: string; - tags_count: any; + tagsCount: any; } export interface IJsonArrayLengthQuery { @@ -57,10 +57,10 @@ export interface IJsonArrayLengthQuery { export type JsonTypeofParams = []; export interface IJsonTypeofResult { - age_type: any; + ageType: any; id: number; - tags_type: any; - username_type: any; + tagsType: any; + usernameType: any; } export interface IJsonTypeofQuery { @@ -71,8 +71,8 @@ export interface IJsonTypeofQuery { export type JsonKeyExistsParams = []; export interface IJsonKeyExistsResult { - has_address: string; - has_username: string; + hasAddress: string; + hasUsername: string; id: number; name: string; } @@ -86,7 +86,7 @@ export type JsonContainsParams = []; export interface IJsonContainsResult { id: number; - is_active: string; + isActive: string; name: string; } @@ -100,7 +100,7 @@ export type JsonBuildObjectTypedParams = []; export interface IJsonBuildObjectTypedResult { id: number; name: string; - user_summary: { id: number; name: string; username: any; email: any }; + userSummary: { id: number; name: string; username: any; email: any }; } export interface IJsonBuildObjectTypedQuery { @@ -124,9 +124,9 @@ export interface IJsonFilterQuery { export type JsonDeepPathParams = []; export interface IJsonDeepPathResult { - app_name: string; - db_host: string; - db_port: number; + appName: string; + dbHost: string; + dbPort: number; id: number; } @@ -134,3 +134,4 @@ export interface IJsonDeepPathQuery { params: JsonDeepPathParams; result: IJsonDeepPathResult; } + From c54638b6ac204c9248f26c2c14756a88ebcdfe56 Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 15:29:52 +1100 Subject: [PATCH 10/19] fix --- docker-compose.yml | 2 +- playpen/db/mysql_migration_5_6.sql | 1 - tests/demo/mysql/json_operations.queries.ts | 36 ---- tests/demo_happy_path.rs | 186 ++++++++++++++++++ .../mysql/json_access_operators.queries.ts | 0 .../mysql/json_access_operators.snapshot.ts | 0 .../general}/mysql/json_access_operators.ts | 6 +- .../mysql/json_array_functions.queries.ts | 0 .../mysql/json_array_functions.snapshot.ts | 0 .../general}/mysql/json_array_functions.ts | 0 .../mysql/json_comprehensive.queries.ts | 0 .../mysql/json_comprehensive.snapshot.ts | 0 .../general}/mysql/json_comprehensive.ts | 0 .../mysql/json_object_functions.queries.ts | 0 .../mysql/json_object_functions.snapshot.ts | 0 .../general}/mysql/json_object_functions.ts | 0 .../general/mysql/json_operations.queries.ts | 12 ++ .../mysql/json_operations.snapshot.ts | 0 .../general/mysql/json_operations.ts | 16 ++ .../postgres/array_operations.queries.ts | 0 .../postgres/array_operations.snapshot.ts | 0 .../general}/postgres/array_operations.ts | 0 .../postgres/json_access_operators.queries.ts | 0 .../json_access_operators.snapshot.ts | 0 .../postgres/json_access_operators.ts | 0 .../postgres/json_array_functions.queries.ts | 0 .../postgres/json_array_functions.snapshot.ts | 0 .../general}/postgres/json_array_functions.ts | 0 .../postgres/json_comprehensive.queries.ts | 0 .../postgres/json_comprehensive.snapshot.ts | 0 .../general}/postgres/json_comprehensive.ts | 0 .../postgres/json_object_functions.queries.ts | 0 .../json_object_functions.snapshot.ts | 0 .../postgres/json_object_functions.ts | 0 .../postgres/jsonb_operations.queries.ts | 0 .../postgres/jsonb_operations.snapshot.ts | 0 .../general}/postgres/jsonb_operations.ts | 0 .../general}/postgres/upsert.queries.ts | 0 .../general}/postgres/upsert.snapshot.ts | 0 .../general}/postgres/upsert.ts | 0 .../modern/mysql/json_access_operators.ts | 33 ++++ .../modern}/mysql/json_operations.ts | 26 +-- 42 files changed, 255 insertions(+), 63 deletions(-) delete mode 100644 tests/demo/mysql/json_operations.queries.ts rename tests/{demo => demo_json/general}/mysql/json_access_operators.queries.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_access_operators.snapshot.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_access_operators.ts (92%) rename tests/{demo => demo_json/general}/mysql/json_array_functions.queries.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_array_functions.snapshot.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_array_functions.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_comprehensive.queries.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_comprehensive.snapshot.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_comprehensive.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_object_functions.queries.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_object_functions.snapshot.ts (100%) rename tests/{demo => demo_json/general}/mysql/json_object_functions.ts (100%) create mode 100644 tests/demo_json/general/mysql/json_operations.queries.ts rename tests/{demo => demo_json/general}/mysql/json_operations.snapshot.ts (100%) create mode 100644 tests/demo_json/general/mysql/json_operations.ts rename tests/{demo => demo_json/general}/postgres/array_operations.queries.ts (100%) rename tests/{demo => demo_json/general}/postgres/array_operations.snapshot.ts (100%) rename tests/{demo => demo_json/general}/postgres/array_operations.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_access_operators.queries.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_access_operators.snapshot.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_access_operators.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_array_functions.queries.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_array_functions.snapshot.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_array_functions.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_comprehensive.queries.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_comprehensive.snapshot.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_comprehensive.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_object_functions.queries.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_object_functions.snapshot.ts (100%) rename tests/{demo => demo_json/general}/postgres/json_object_functions.ts (100%) rename tests/{demo => demo_json/general}/postgres/jsonb_operations.queries.ts (100%) rename tests/{demo => demo_json/general}/postgres/jsonb_operations.snapshot.ts (100%) rename tests/{demo => demo_json/general}/postgres/jsonb_operations.ts (100%) rename tests/{demo => demo_json/general}/postgres/upsert.queries.ts (100%) rename tests/{demo => demo_json/general}/postgres/upsert.snapshot.ts (100%) rename tests/{demo => demo_json/general}/postgres/upsert.ts (100%) create mode 100644 tests/demo_json/modern/mysql/json_access_operators.ts rename tests/{demo => demo_json/modern}/mysql/json_operations.ts (59%) diff --git a/docker-compose.yml b/docker-compose.yml index 77e78534..2aae8c6b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,7 @@ services: mysql: platform: linux/amd64 - image: mysql:5.7 + image: mysql:${MYSQL_VERSION:-8} restart: always volumes: - ./playpen/db/${MYSQL_MIGRATION_FILE:-mysql_migration.sql}:/docker-entrypoint-initdb.d/mysql_migration.sql diff --git a/playpen/db/mysql_migration_5_6.sql b/playpen/db/mysql_migration_5_6.sql index 4040c66a..0c805fe2 100644 --- a/playpen/db/mysql_migration_5_6.sql +++ b/playpen/db/mysql_migration_5_6.sql @@ -159,7 +159,6 @@ CREATE TABLE random ( json1 TEXT ); - -- JSON Test Data Table -- This table contains various JSON structures for testing JSON operators and functions CREATE TABLE json_test_data ( diff --git a/tests/demo/mysql/json_operations.queries.ts b/tests/demo/mysql/json_operations.queries.ts deleted file mode 100644 index ec3e5c7d..00000000 --- a/tests/demo/mysql/json_operations.queries.ts +++ /dev/null @@ -1,36 +0,0 @@ -export type JsonObjectBasicParams = []; - -export interface IJsonObjectBasicResult { - id: number; - itemJson: any; -} - -export interface IJsonObjectBasicQuery { - params: JsonObjectBasicParams; - result: IJsonObjectBasicResult; -} - -export type JsonArrayAggregationParams = []; - -export interface IJsonArrayAggregationResult { - items: any; - rarity: string | null; -} - -export interface IJsonArrayAggregationQuery { - params: JsonArrayAggregationParams; - result: IJsonArrayAggregationResult; -} - -export type JsonOperatorsSelectParams = []; - -export interface IJsonOperatorsSelectResult { - extractedName: any; - id: number; - name: string; -} - -export interface IJsonOperatorsSelectQuery { - params: JsonOperatorsSelectParams; - result: IJsonOperatorsSelectResult; -} diff --git a/tests/demo_happy_path.rs b/tests/demo_happy_path.rs index 33c27036..17776cb8 100644 --- a/tests/demo_happy_path.rs +++ b/tests/demo_happy_path.rs @@ -2,11 +2,22 @@ mod demo_happy_path_tests { use assert_cmd::cargo::cargo_bin_cmd; use pretty_assertions::assert_eq; + use std::env; use std::env::current_dir; use std::fs; use std::io::Write; use walkdir::WalkDir; + fn mysql_supports_json() -> bool { + // MySQL 5.7.8+ supports JSON functions + // Default to true if MYSQL_VERSION not set + env::var("MYSQL_VERSION") + .ok() + .and_then(|v| v.split('.').next().and_then(|major| major.parse::().ok())) + .map(|major| major >= 6 || major == 5) // Assume 5.7+ if version is 5 + .unwrap_or(true) + } + #[test] fn all_demo_should_pass() -> Result<(), Box> { // SETUP @@ -90,6 +101,181 @@ mod demo_happy_path_tests { Ok(()) } + + #[test] + fn all_demo_json_general() -> Result<(), Box> { + // SETUP + let root_path = current_dir().unwrap(); + let demo_path = root_path.join("tests/demo_json/general"); + + // EXECUTE - Generate types for .ts files + let mut cmd = cargo_bin_cmd!("sqlx-ts"); + cmd + .arg(demo_path.to_str().unwrap()) + .arg("--ext=ts") + .arg("--config=.sqlxrc.sample.json") + .arg("-g"); + + // ASSERT + cmd + .assert() + .success() + .stdout(predicates::str::contains("No SQL errors detected!")); + + // Also generate types for other extensions in file_extensions directory + let file_extensions_path = demo_path.join("file_extensions"); + if file_extensions_path.exists() { + for ext in &["js", "mts", "cts", "mjs", "cjs"] { + let mut cmd = cargo_bin_cmd!("sqlx-ts"); + cmd + .arg(file_extensions_path.to_str().unwrap()) + .arg(format!("--ext={}", ext)) + .arg("--config=.sqlxrc.sample.json") + .arg("-g"); + cmd + .assert() + .success() + .stdout(predicates::str::contains("No SQL errors detected!")); + } + } + + // Also generate types for SQL files + let sql_files_path = demo_path.join("sql_files"); + if sql_files_path.exists() { + let mut cmd = cargo_bin_cmd!("sqlx-ts"); + cmd + .arg(sql_files_path.to_str().unwrap()) + .arg("--ext=sql") + .arg("--config=.sqlxrc.sample.json") + .arg("-g"); + cmd + .assert() + .success() + .stdout(predicates::str::contains("No SQL errors detected!")); + } + + // Verify all generated types match snapshots + for entry in WalkDir::new(demo_path) { + if entry.is_ok() { + let entry = entry.unwrap(); + let path = entry.path(); + let parent = entry.path().parent().unwrap(); + let file_name = path.file_name().unwrap().to_str().unwrap().to_string(); + + if path.is_file() && file_name.ends_with(".queries.ts") { + let base_file_name = file_name.split('.').collect::>(); + let base_file_name = base_file_name.first().unwrap(); + let snapshot_path = parent.join(format!("{base_file_name}.snapshot.ts")); + + let generated_types = fs::read_to_string(path)?; + + if !snapshot_path.exists() { + let mut snapshot_file = fs::File::create(&snapshot_path)?; + writeln!(snapshot_file, "{generated_types}")?; + } + + assert_eq!( + generated_types.trim().to_string().trim(), + fs::read_to_string(&snapshot_path)?.to_string().trim(), + ) + } + } + } + + Ok(()) + } + + + #[test] + fn all_demo_json_modern() -> Result<(), Box> { + // SETUP + let mysql_version = env::var("MYSQL_VERSION").ok(); + + if mysql_version == Some("5.6".to_string()) { + return Ok(()); // Skip test for MySQL 5.6 which doesn't support JSON functions + } + + let root_path = current_dir().unwrap(); + let demo_path = root_path.join("tests/demo_json/modern"); + + // EXECUTE - Generate types for .ts files + let mut cmd = cargo_bin_cmd!("sqlx-ts"); + cmd + .arg(demo_path.to_str().unwrap()) + .arg("--ext=ts") + .arg("--config=.sqlxrc.sample.json") + .arg("-g"); + + // ASSERT + cmd + .assert() + .success() + .stdout(predicates::str::contains("No SQL errors detected!")); + + // Also generate types for other extensions in file_extensions directory + let file_extensions_path = demo_path.join("file_extensions"); + if file_extensions_path.exists() { + for ext in &["js", "mts", "cts", "mjs", "cjs"] { + let mut cmd = cargo_bin_cmd!("sqlx-ts"); + cmd + .arg(file_extensions_path.to_str().unwrap()) + .arg(format!("--ext={}", ext)) + .arg("--config=.sqlxrc.sample.json") + .arg("-g"); + cmd + .assert() + .success() + .stdout(predicates::str::contains("No SQL errors detected!")); + } + } + + // Also generate types for SQL files + let sql_files_path = demo_path.join("sql_files"); + if sql_files_path.exists() { + let mut cmd = cargo_bin_cmd!("sqlx-ts"); + cmd + .arg(sql_files_path.to_str().unwrap()) + .arg("--ext=sql") + .arg("--config=.sqlxrc.sample.json") + .arg("-g"); + cmd + .assert() + .success() + .stdout(predicates::str::contains("No SQL errors detected!")); + } + + // Verify all generated types match snapshots + for entry in WalkDir::new(demo_path) { + if entry.is_ok() { + let entry = entry.unwrap(); + let path = entry.path(); + let parent = entry.path().parent().unwrap(); + let file_name = path.file_name().unwrap().to_str().unwrap().to_string(); + + if path.is_file() && file_name.ends_with(".queries.ts") { + let base_file_name = file_name.split('.').collect::>(); + let base_file_name = base_file_name.first().unwrap(); + let snapshot_path = parent.join(format!("{base_file_name}.snapshot.ts")); + + let generated_types = fs::read_to_string(path)?; + + if !snapshot_path.exists() { + let mut snapshot_file = fs::File::create(&snapshot_path)?; + writeln!(snapshot_file, "{generated_types}")?; + } + + assert_eq!( + generated_types.trim().to_string().trim(), + fs::read_to_string(&snapshot_path)?.to_string().trim(), + ) + } + } + } + + Ok(()) + } + + #[test] fn test_js_files() -> Result<(), Box> { // SETUP diff --git a/tests/demo/mysql/json_access_operators.queries.ts b/tests/demo_json/general/mysql/json_access_operators.queries.ts similarity index 100% rename from tests/demo/mysql/json_access_operators.queries.ts rename to tests/demo_json/general/mysql/json_access_operators.queries.ts diff --git a/tests/demo/mysql/json_access_operators.snapshot.ts b/tests/demo_json/general/mysql/json_access_operators.snapshot.ts similarity index 100% rename from tests/demo/mysql/json_access_operators.snapshot.ts rename to tests/demo_json/general/mysql/json_access_operators.snapshot.ts diff --git a/tests/demo/mysql/json_access_operators.ts b/tests/demo_json/general/mysql/json_access_operators.ts similarity index 92% rename from tests/demo/mysql/json_access_operators.ts rename to tests/demo_json/general/mysql/json_access_operators.ts index 4722dc85..1d8559d3 100644 --- a/tests/demo/mysql/json_access_operators.ts +++ b/tests/demo_json/general/mysql/json_access_operators.ts @@ -38,10 +38,7 @@ SELECT json_test_data.id AS id, json_test_data.name AS name, data -> '$.address' AS address_json, - data -> '$.address.city' AS city_json, - JSON_UNQUOTE(data -> '$.address.city') AS city, - JSON_UNQUOTE(data -> '$.address.zipCode') AS zip_code, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.street')) AS street + data -> '$.address.city' AS city_json FROM json_test_data WHERE json_test_data.name = 'user_with_address' ` @@ -56,7 +53,6 @@ SELECT data -> '$.items' AS items_json, data -> '$.items[0]' AS first_item_json, data -> '$.items[1]' AS second_item_json, - JSON_UNQUOTE(data -> '$.items[0].name') AS first_item_name, CAST(data -> '$.items[0].price' AS DECIMAL(10,2)) AS first_item_price FROM json_test_data WHERE json_test_data.name = 'shopping_cart' diff --git a/tests/demo/mysql/json_array_functions.queries.ts b/tests/demo_json/general/mysql/json_array_functions.queries.ts similarity index 100% rename from tests/demo/mysql/json_array_functions.queries.ts rename to tests/demo_json/general/mysql/json_array_functions.queries.ts diff --git a/tests/demo/mysql/json_array_functions.snapshot.ts b/tests/demo_json/general/mysql/json_array_functions.snapshot.ts similarity index 100% rename from tests/demo/mysql/json_array_functions.snapshot.ts rename to tests/demo_json/general/mysql/json_array_functions.snapshot.ts diff --git a/tests/demo/mysql/json_array_functions.ts b/tests/demo_json/general/mysql/json_array_functions.ts similarity index 100% rename from tests/demo/mysql/json_array_functions.ts rename to tests/demo_json/general/mysql/json_array_functions.ts diff --git a/tests/demo/mysql/json_comprehensive.queries.ts b/tests/demo_json/general/mysql/json_comprehensive.queries.ts similarity index 100% rename from tests/demo/mysql/json_comprehensive.queries.ts rename to tests/demo_json/general/mysql/json_comprehensive.queries.ts diff --git a/tests/demo/mysql/json_comprehensive.snapshot.ts b/tests/demo_json/general/mysql/json_comprehensive.snapshot.ts similarity index 100% rename from tests/demo/mysql/json_comprehensive.snapshot.ts rename to tests/demo_json/general/mysql/json_comprehensive.snapshot.ts diff --git a/tests/demo/mysql/json_comprehensive.ts b/tests/demo_json/general/mysql/json_comprehensive.ts similarity index 100% rename from tests/demo/mysql/json_comprehensive.ts rename to tests/demo_json/general/mysql/json_comprehensive.ts diff --git a/tests/demo/mysql/json_object_functions.queries.ts b/tests/demo_json/general/mysql/json_object_functions.queries.ts similarity index 100% rename from tests/demo/mysql/json_object_functions.queries.ts rename to tests/demo_json/general/mysql/json_object_functions.queries.ts diff --git a/tests/demo/mysql/json_object_functions.snapshot.ts b/tests/demo_json/general/mysql/json_object_functions.snapshot.ts similarity index 100% rename from tests/demo/mysql/json_object_functions.snapshot.ts rename to tests/demo_json/general/mysql/json_object_functions.snapshot.ts diff --git a/tests/demo/mysql/json_object_functions.ts b/tests/demo_json/general/mysql/json_object_functions.ts similarity index 100% rename from tests/demo/mysql/json_object_functions.ts rename to tests/demo_json/general/mysql/json_object_functions.ts diff --git a/tests/demo_json/general/mysql/json_operations.queries.ts b/tests/demo_json/general/mysql/json_operations.queries.ts new file mode 100644 index 00000000..2392c7e4 --- /dev/null +++ b/tests/demo_json/general/mysql/json_operations.queries.ts @@ -0,0 +1,12 @@ +export type JsonOperatorsSelectParams = []; + +export interface IJsonOperatorsSelectResult { + extractedName: any; + id: number; + name: string; +} + +export interface IJsonOperatorsSelectQuery { + params: JsonOperatorsSelectParams; + result: IJsonOperatorsSelectResult; +} diff --git a/tests/demo/mysql/json_operations.snapshot.ts b/tests/demo_json/general/mysql/json_operations.snapshot.ts similarity index 100% rename from tests/demo/mysql/json_operations.snapshot.ts rename to tests/demo_json/general/mysql/json_operations.snapshot.ts diff --git a/tests/demo_json/general/mysql/json_operations.ts b/tests/demo_json/general/mysql/json_operations.ts new file mode 100644 index 00000000..080c2003 --- /dev/null +++ b/tests/demo_json/general/mysql/json_operations.ts @@ -0,0 +1,16 @@ +import { sql } from 'sqlx-ts' + +// JSON operators in SELECT - extract values +const jsonOperatorsSelect = sql` +-- @db: db_mysql +-- @name: json operators select +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT( + JSON_OBJECT('id', json_test_data.id, 'name', json_test_data.name), + '$.name' + )) AS extracted_name +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` diff --git a/tests/demo/postgres/array_operations.queries.ts b/tests/demo_json/general/postgres/array_operations.queries.ts similarity index 100% rename from tests/demo/postgres/array_operations.queries.ts rename to tests/demo_json/general/postgres/array_operations.queries.ts diff --git a/tests/demo/postgres/array_operations.snapshot.ts b/tests/demo_json/general/postgres/array_operations.snapshot.ts similarity index 100% rename from tests/demo/postgres/array_operations.snapshot.ts rename to tests/demo_json/general/postgres/array_operations.snapshot.ts diff --git a/tests/demo/postgres/array_operations.ts b/tests/demo_json/general/postgres/array_operations.ts similarity index 100% rename from tests/demo/postgres/array_operations.ts rename to tests/demo_json/general/postgres/array_operations.ts diff --git a/tests/demo/postgres/json_access_operators.queries.ts b/tests/demo_json/general/postgres/json_access_operators.queries.ts similarity index 100% rename from tests/demo/postgres/json_access_operators.queries.ts rename to tests/demo_json/general/postgres/json_access_operators.queries.ts diff --git a/tests/demo/postgres/json_access_operators.snapshot.ts b/tests/demo_json/general/postgres/json_access_operators.snapshot.ts similarity index 100% rename from tests/demo/postgres/json_access_operators.snapshot.ts rename to tests/demo_json/general/postgres/json_access_operators.snapshot.ts diff --git a/tests/demo/postgres/json_access_operators.ts b/tests/demo_json/general/postgres/json_access_operators.ts similarity index 100% rename from tests/demo/postgres/json_access_operators.ts rename to tests/demo_json/general/postgres/json_access_operators.ts diff --git a/tests/demo/postgres/json_array_functions.queries.ts b/tests/demo_json/general/postgres/json_array_functions.queries.ts similarity index 100% rename from tests/demo/postgres/json_array_functions.queries.ts rename to tests/demo_json/general/postgres/json_array_functions.queries.ts diff --git a/tests/demo/postgres/json_array_functions.snapshot.ts b/tests/demo_json/general/postgres/json_array_functions.snapshot.ts similarity index 100% rename from tests/demo/postgres/json_array_functions.snapshot.ts rename to tests/demo_json/general/postgres/json_array_functions.snapshot.ts diff --git a/tests/demo/postgres/json_array_functions.ts b/tests/demo_json/general/postgres/json_array_functions.ts similarity index 100% rename from tests/demo/postgres/json_array_functions.ts rename to tests/demo_json/general/postgres/json_array_functions.ts diff --git a/tests/demo/postgres/json_comprehensive.queries.ts b/tests/demo_json/general/postgres/json_comprehensive.queries.ts similarity index 100% rename from tests/demo/postgres/json_comprehensive.queries.ts rename to tests/demo_json/general/postgres/json_comprehensive.queries.ts diff --git a/tests/demo/postgres/json_comprehensive.snapshot.ts b/tests/demo_json/general/postgres/json_comprehensive.snapshot.ts similarity index 100% rename from tests/demo/postgres/json_comprehensive.snapshot.ts rename to tests/demo_json/general/postgres/json_comprehensive.snapshot.ts diff --git a/tests/demo/postgres/json_comprehensive.ts b/tests/demo_json/general/postgres/json_comprehensive.ts similarity index 100% rename from tests/demo/postgres/json_comprehensive.ts rename to tests/demo_json/general/postgres/json_comprehensive.ts diff --git a/tests/demo/postgres/json_object_functions.queries.ts b/tests/demo_json/general/postgres/json_object_functions.queries.ts similarity index 100% rename from tests/demo/postgres/json_object_functions.queries.ts rename to tests/demo_json/general/postgres/json_object_functions.queries.ts diff --git a/tests/demo/postgres/json_object_functions.snapshot.ts b/tests/demo_json/general/postgres/json_object_functions.snapshot.ts similarity index 100% rename from tests/demo/postgres/json_object_functions.snapshot.ts rename to tests/demo_json/general/postgres/json_object_functions.snapshot.ts diff --git a/tests/demo/postgres/json_object_functions.ts b/tests/demo_json/general/postgres/json_object_functions.ts similarity index 100% rename from tests/demo/postgres/json_object_functions.ts rename to tests/demo_json/general/postgres/json_object_functions.ts diff --git a/tests/demo/postgres/jsonb_operations.queries.ts b/tests/demo_json/general/postgres/jsonb_operations.queries.ts similarity index 100% rename from tests/demo/postgres/jsonb_operations.queries.ts rename to tests/demo_json/general/postgres/jsonb_operations.queries.ts diff --git a/tests/demo/postgres/jsonb_operations.snapshot.ts b/tests/demo_json/general/postgres/jsonb_operations.snapshot.ts similarity index 100% rename from tests/demo/postgres/jsonb_operations.snapshot.ts rename to tests/demo_json/general/postgres/jsonb_operations.snapshot.ts diff --git a/tests/demo/postgres/jsonb_operations.ts b/tests/demo_json/general/postgres/jsonb_operations.ts similarity index 100% rename from tests/demo/postgres/jsonb_operations.ts rename to tests/demo_json/general/postgres/jsonb_operations.ts diff --git a/tests/demo/postgres/upsert.queries.ts b/tests/demo_json/general/postgres/upsert.queries.ts similarity index 100% rename from tests/demo/postgres/upsert.queries.ts rename to tests/demo_json/general/postgres/upsert.queries.ts diff --git a/tests/demo/postgres/upsert.snapshot.ts b/tests/demo_json/general/postgres/upsert.snapshot.ts similarity index 100% rename from tests/demo/postgres/upsert.snapshot.ts rename to tests/demo_json/general/postgres/upsert.snapshot.ts diff --git a/tests/demo/postgres/upsert.ts b/tests/demo_json/general/postgres/upsert.ts similarity index 100% rename from tests/demo/postgres/upsert.ts rename to tests/demo_json/general/postgres/upsert.ts diff --git a/tests/demo_json/modern/mysql/json_access_operators.ts b/tests/demo_json/modern/mysql/json_access_operators.ts new file mode 100644 index 00000000..d1130170 --- /dev/null +++ b/tests/demo_json/modern/mysql/json_access_operators.ts @@ -0,0 +1,33 @@ +import { sql } from 'sqlx-ts' + +// Test nested field access with JSON_EXTRACT +const jsonNestedAccess = sql` +-- @db: db_mysql +-- @name: json nested access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data -> '$.address' AS address_json, + data -> '$.address.city' AS city_json, + JSON_UNQUOTE(data -> '$.address.city') AS city, + JSON_UNQUOTE(data -> '$.address.zipCode') AS zip_code, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.street')) AS street +FROM json_test_data +WHERE json_test_data.name = 'user_with_address' +` + +// Test array element access by index +const jsonArrayAccess = sql` +-- @db: db_mysql +-- @name: json array access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data -> '$.items' AS items_json, + data -> '$.items[0]' AS first_item_json, + data -> '$.items[1]' AS second_item_json, + JSON_UNQUOTE(data -> '$.items[0].name') AS first_item_name, + CAST(data -> '$.items[0].price' AS DECIMAL(10,2)) AS first_item_price +FROM json_test_data +WHERE json_test_data.name = 'shopping_cart' +` diff --git a/tests/demo/mysql/json_operations.ts b/tests/demo_json/modern/mysql/json_operations.ts similarity index 59% rename from tests/demo/mysql/json_operations.ts rename to tests/demo_json/modern/mysql/json_operations.ts index 1fd1625f..9b6ecb4c 100644 --- a/tests/demo/mysql/json_operations.ts +++ b/tests/demo_json/modern/mysql/json_operations.ts @@ -1,14 +1,5 @@ import { sql } from 'sqlx-ts' -// JSON_OBJECT basic - build object from columns -const jsonObjectBasic = sql` --- @db: db_mysql --- @name: json object basic -SELECT - items.id AS id, - JSON_OBJECT('id', items.id, 'name', items.name, 'rarity', items.rarity) AS item_json -FROM items -` // JSON_ARRAYAGG for aggregation - aggregate rows into JSON array const jsonArrayAggregation = sql` @@ -21,17 +12,12 @@ FROM items GROUP BY items.rarity ` -// JSON operators in SELECT - extract values -const jsonOperatorsSelect = sql` +// JSON_OBJECT basic - build object from columns +const jsonObjectBasic = sql` -- @db: db_mysql --- @name: json operators select +-- @name: json object basic SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_UNQUOTE(JSON_EXTRACT( - JSON_OBJECT('id', json_test_data.id, 'name', json_test_data.name), - '$.name' - )) AS extracted_name -FROM json_test_data -WHERE json_test_data.name = 'user_profile' + items.id AS id, + JSON_OBJECT('id', items.id, 'name', items.name, 'rarity', items.rarity) AS item_json +FROM items ` From ed60bc2478938936558e67e350fbc0792776bdcd Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 15:38:31 +1100 Subject: [PATCH 11/19] done --- tests/demo_happy_path.rs | 16 +++++-------- .../modern/mysql/json_operations.queries.ts | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 tests/demo_json/modern/mysql/json_operations.queries.ts diff --git a/tests/demo_happy_path.rs b/tests/demo_happy_path.rs index 17776cb8..8e7f2cac 100644 --- a/tests/demo_happy_path.rs +++ b/tests/demo_happy_path.rs @@ -8,16 +8,6 @@ mod demo_happy_path_tests { use std::io::Write; use walkdir::WalkDir; - fn mysql_supports_json() -> bool { - // MySQL 5.7.8+ supports JSON functions - // Default to true if MYSQL_VERSION not set - env::var("MYSQL_VERSION") - .ok() - .and_then(|v| v.split('.').next().and_then(|major| major.parse::().ok())) - .map(|major| major >= 6 || major == 5) // Assume 5.7+ if version is 5 - .unwrap_or(true) - } - #[test] fn all_demo_should_pass() -> Result<(), Box> { // SETUP @@ -105,6 +95,12 @@ mod demo_happy_path_tests { #[test] fn all_demo_json_general() -> Result<(), Box> { // SETUP + let mysql_version = env::var("MYSQL_VERSION").ok(); + + if mysql_version == Some("5.6".to_string()) { + return Ok(()); // Skip test for MySQL 5.6 which doesn't support JSON functions + } + let root_path = current_dir().unwrap(); let demo_path = root_path.join("tests/demo_json/general"); diff --git a/tests/demo_json/modern/mysql/json_operations.queries.ts b/tests/demo_json/modern/mysql/json_operations.queries.ts new file mode 100644 index 00000000..110fa793 --- /dev/null +++ b/tests/demo_json/modern/mysql/json_operations.queries.ts @@ -0,0 +1,23 @@ +export type JsonArrayAggregationParams = []; + +export interface IJsonArrayAggregationResult { + items: any; + rarity: string | null; +} + +export interface IJsonArrayAggregationQuery { + params: JsonArrayAggregationParams; + result: IJsonArrayAggregationResult; +} + +export type JsonObjectBasicParams = []; + +export interface IJsonObjectBasicResult { + id: number; + itemJson: any; +} + +export interface IJsonObjectBasicQuery { + params: JsonObjectBasicParams; + result: IJsonObjectBasicResult; +} From 10dc6e33384051735c5b332eb7ff339597fc40d1 Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 15:40:58 +1100 Subject: [PATCH 12/19] remove mysql 5.6 check from general --- tests/demo_happy_path.rs | 181 +++------------------------------------ 1 file changed, 12 insertions(+), 169 deletions(-) diff --git a/tests/demo_happy_path.rs b/tests/demo_happy_path.rs index 8e7f2cac..00b9b03f 100644 --- a/tests/demo_happy_path.rs +++ b/tests/demo_happy_path.rs @@ -6,14 +6,10 @@ mod demo_happy_path_tests { use std::env::current_dir; use std::fs; use std::io::Write; + use std::path::Path; use walkdir::WalkDir; - #[test] - fn all_demo_should_pass() -> Result<(), Box> { - // SETUP - let root_path = current_dir().unwrap(); - let demo_path = root_path.join("tests/demo"); - + fn run_demo_test(demo_path: &Path) -> Result<(), Box> { // EXECUTE - Generate types for .ts files let mut cmd = cargo_bin_cmd!("sqlx-ts"); cmd @@ -91,184 +87,31 @@ mod demo_happy_path_tests { Ok(()) } - #[test] - fn all_demo_json_general() -> Result<(), Box> { - // SETUP - let mysql_version = env::var("MYSQL_VERSION").ok(); + fn all_demo_should_pass() -> Result<(), Box> { + let root_path = current_dir().unwrap(); + let demo_path = root_path.join("tests/demo"); + run_demo_test(&demo_path) + } - if mysql_version == Some("5.6".to_string()) { - return Ok(()); // Skip test for MySQL 5.6 which doesn't support JSON functions - } + #[test] + fn all_demo_json_general() -> Result<(), Box> { let root_path = current_dir().unwrap(); let demo_path = root_path.join("tests/demo_json/general"); - - // EXECUTE - Generate types for .ts files - let mut cmd = cargo_bin_cmd!("sqlx-ts"); - cmd - .arg(demo_path.to_str().unwrap()) - .arg("--ext=ts") - .arg("--config=.sqlxrc.sample.json") - .arg("-g"); - - // ASSERT - cmd - .assert() - .success() - .stdout(predicates::str::contains("No SQL errors detected!")); - - // Also generate types for other extensions in file_extensions directory - let file_extensions_path = demo_path.join("file_extensions"); - if file_extensions_path.exists() { - for ext in &["js", "mts", "cts", "mjs", "cjs"] { - let mut cmd = cargo_bin_cmd!("sqlx-ts"); - cmd - .arg(file_extensions_path.to_str().unwrap()) - .arg(format!("--ext={}", ext)) - .arg("--config=.sqlxrc.sample.json") - .arg("-g"); - cmd - .assert() - .success() - .stdout(predicates::str::contains("No SQL errors detected!")); - } - } - - // Also generate types for SQL files - let sql_files_path = demo_path.join("sql_files"); - if sql_files_path.exists() { - let mut cmd = cargo_bin_cmd!("sqlx-ts"); - cmd - .arg(sql_files_path.to_str().unwrap()) - .arg("--ext=sql") - .arg("--config=.sqlxrc.sample.json") - .arg("-g"); - cmd - .assert() - .success() - .stdout(predicates::str::contains("No SQL errors detected!")); - } - - // Verify all generated types match snapshots - for entry in WalkDir::new(demo_path) { - if entry.is_ok() { - let entry = entry.unwrap(); - let path = entry.path(); - let parent = entry.path().parent().unwrap(); - let file_name = path.file_name().unwrap().to_str().unwrap().to_string(); - - if path.is_file() && file_name.ends_with(".queries.ts") { - let base_file_name = file_name.split('.').collect::>(); - let base_file_name = base_file_name.first().unwrap(); - let snapshot_path = parent.join(format!("{base_file_name}.snapshot.ts")); - - let generated_types = fs::read_to_string(path)?; - - if !snapshot_path.exists() { - let mut snapshot_file = fs::File::create(&snapshot_path)?; - writeln!(snapshot_file, "{generated_types}")?; - } - - assert_eq!( - generated_types.trim().to_string().trim(), - fs::read_to_string(&snapshot_path)?.to_string().trim(), - ) - } - } - } - - Ok(()) + run_demo_test(&demo_path) } #[test] fn all_demo_json_modern() -> Result<(), Box> { - // SETUP - let mysql_version = env::var("MYSQL_VERSION").ok(); - - if mysql_version == Some("5.6".to_string()) { + if env::var("MYSQL_VERSION").ok() == Some("5.6".to_string()) { return Ok(()); // Skip test for MySQL 5.6 which doesn't support JSON functions } let root_path = current_dir().unwrap(); let demo_path = root_path.join("tests/demo_json/modern"); - - // EXECUTE - Generate types for .ts files - let mut cmd = cargo_bin_cmd!("sqlx-ts"); - cmd - .arg(demo_path.to_str().unwrap()) - .arg("--ext=ts") - .arg("--config=.sqlxrc.sample.json") - .arg("-g"); - - // ASSERT - cmd - .assert() - .success() - .stdout(predicates::str::contains("No SQL errors detected!")); - - // Also generate types for other extensions in file_extensions directory - let file_extensions_path = demo_path.join("file_extensions"); - if file_extensions_path.exists() { - for ext in &["js", "mts", "cts", "mjs", "cjs"] { - let mut cmd = cargo_bin_cmd!("sqlx-ts"); - cmd - .arg(file_extensions_path.to_str().unwrap()) - .arg(format!("--ext={}", ext)) - .arg("--config=.sqlxrc.sample.json") - .arg("-g"); - cmd - .assert() - .success() - .stdout(predicates::str::contains("No SQL errors detected!")); - } - } - - // Also generate types for SQL files - let sql_files_path = demo_path.join("sql_files"); - if sql_files_path.exists() { - let mut cmd = cargo_bin_cmd!("sqlx-ts"); - cmd - .arg(sql_files_path.to_str().unwrap()) - .arg("--ext=sql") - .arg("--config=.sqlxrc.sample.json") - .arg("-g"); - cmd - .assert() - .success() - .stdout(predicates::str::contains("No SQL errors detected!")); - } - - // Verify all generated types match snapshots - for entry in WalkDir::new(demo_path) { - if entry.is_ok() { - let entry = entry.unwrap(); - let path = entry.path(); - let parent = entry.path().parent().unwrap(); - let file_name = path.file_name().unwrap().to_str().unwrap().to_string(); - - if path.is_file() && file_name.ends_with(".queries.ts") { - let base_file_name = file_name.split('.').collect::>(); - let base_file_name = base_file_name.first().unwrap(); - let snapshot_path = parent.join(format!("{base_file_name}.snapshot.ts")); - - let generated_types = fs::read_to_string(path)?; - - if !snapshot_path.exists() { - let mut snapshot_file = fs::File::create(&snapshot_path)?; - writeln!(snapshot_file, "{generated_types}")?; - } - - assert_eq!( - generated_types.trim().to_string().trim(), - fs::read_to_string(&snapshot_path)?.to_string().trim(), - ) - } - } - } - - Ok(()) + run_demo_test(&demo_path) } From 085d555b0ec2d105d9d6516aacc49cc64d083eb5 Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 15:45:23 +1100 Subject: [PATCH 13/19] remove mysql 5.6 check from general --- .../mysql/json_access_operators.queries.ts | 128 ----------- .../mysql/json_access_operators.snapshot.ts | 129 ----------- .../mysql/json_array_functions.queries.ts | 130 ----------- .../mysql/json_array_functions.snapshot.ts | 131 ----------- .../general/mysql/json_array_functions.ts | 127 ----------- .../mysql/json_comprehensive.queries.ts | 175 --------------- .../mysql/json_comprehensive.snapshot.ts | 176 --------------- .../general/mysql/json_comprehensive.ts | 171 -------------- .../mysql/json_object_functions.queries.ts | 210 ----------------- .../mysql/json_object_functions.snapshot.ts | 211 ------------------ .../general/mysql/json_object_functions.ts | 206 ----------------- .../general/mysql/json_operations.snapshot.ts | 37 --- .../modern/mysql/json_access_operators.ts | 33 --- .../modern/mysql/json_operations.queries.ts | 23 -- .../demo_json/modern/mysql/json_operations.ts | 23 -- .../mysql/json_access_operators.ts | 0 .../mysql/json_operations.queries.ts | 0 .../{general => }/mysql/json_operations.ts | 0 .../postgres/array_operations.queries.ts | 0 .../postgres/array_operations.snapshot.ts | 0 .../postgres/array_operations.ts | 0 .../postgres/json_access_operators.queries.ts | 0 .../json_access_operators.snapshot.ts | 0 .../postgres/json_access_operators.ts | 0 .../postgres/json_array_functions.queries.ts | 0 .../postgres/json_array_functions.snapshot.ts | 0 .../postgres/json_array_functions.ts | 0 .../postgres/json_comprehensive.queries.ts | 0 .../postgres/json_comprehensive.snapshot.ts | 0 .../postgres/json_comprehensive.ts | 0 .../postgres/json_object_functions.queries.ts | 0 .../json_object_functions.snapshot.ts | 0 .../postgres/json_object_functions.ts | 0 .../postgres/jsonb_operations.queries.ts | 0 .../postgres/jsonb_operations.snapshot.ts | 0 .../postgres/jsonb_operations.ts | 0 .../{general => }/postgres/upsert.queries.ts | 0 .../{general => }/postgres/upsert.snapshot.ts | 0 .../{general => }/postgres/upsert.ts | 0 39 files changed, 1910 deletions(-) delete mode 100644 tests/demo_json/general/mysql/json_access_operators.queries.ts delete mode 100644 tests/demo_json/general/mysql/json_access_operators.snapshot.ts delete mode 100644 tests/demo_json/general/mysql/json_array_functions.queries.ts delete mode 100644 tests/demo_json/general/mysql/json_array_functions.snapshot.ts delete mode 100644 tests/demo_json/general/mysql/json_array_functions.ts delete mode 100644 tests/demo_json/general/mysql/json_comprehensive.queries.ts delete mode 100644 tests/demo_json/general/mysql/json_comprehensive.snapshot.ts delete mode 100644 tests/demo_json/general/mysql/json_comprehensive.ts delete mode 100644 tests/demo_json/general/mysql/json_object_functions.queries.ts delete mode 100644 tests/demo_json/general/mysql/json_object_functions.snapshot.ts delete mode 100644 tests/demo_json/general/mysql/json_object_functions.ts delete mode 100644 tests/demo_json/general/mysql/json_operations.snapshot.ts delete mode 100644 tests/demo_json/modern/mysql/json_access_operators.ts delete mode 100644 tests/demo_json/modern/mysql/json_operations.queries.ts delete mode 100644 tests/demo_json/modern/mysql/json_operations.ts rename tests/demo_json/{general => }/mysql/json_access_operators.ts (100%) rename tests/demo_json/{general => }/mysql/json_operations.queries.ts (100%) rename tests/demo_json/{general => }/mysql/json_operations.ts (100%) rename tests/demo_json/{general => }/postgres/array_operations.queries.ts (100%) rename tests/demo_json/{general => }/postgres/array_operations.snapshot.ts (100%) rename tests/demo_json/{general => }/postgres/array_operations.ts (100%) rename tests/demo_json/{general => }/postgres/json_access_operators.queries.ts (100%) rename tests/demo_json/{general => }/postgres/json_access_operators.snapshot.ts (100%) rename tests/demo_json/{general => }/postgres/json_access_operators.ts (100%) rename tests/demo_json/{general => }/postgres/json_array_functions.queries.ts (100%) rename tests/demo_json/{general => }/postgres/json_array_functions.snapshot.ts (100%) rename tests/demo_json/{general => }/postgres/json_array_functions.ts (100%) rename tests/demo_json/{general => }/postgres/json_comprehensive.queries.ts (100%) rename tests/demo_json/{general => }/postgres/json_comprehensive.snapshot.ts (100%) rename tests/demo_json/{general => }/postgres/json_comprehensive.ts (100%) rename tests/demo_json/{general => }/postgres/json_object_functions.queries.ts (100%) rename tests/demo_json/{general => }/postgres/json_object_functions.snapshot.ts (100%) rename tests/demo_json/{general => }/postgres/json_object_functions.ts (100%) rename tests/demo_json/{general => }/postgres/jsonb_operations.queries.ts (100%) rename tests/demo_json/{general => }/postgres/jsonb_operations.snapshot.ts (100%) rename tests/demo_json/{general => }/postgres/jsonb_operations.ts (100%) rename tests/demo_json/{general => }/postgres/upsert.queries.ts (100%) rename tests/demo_json/{general => }/postgres/upsert.snapshot.ts (100%) rename tests/demo_json/{general => }/postgres/upsert.ts (100%) diff --git a/tests/demo_json/general/mysql/json_access_operators.queries.ts b/tests/demo_json/general/mysql/json_access_operators.queries.ts deleted file mode 100644 index 81a2595e..00000000 --- a/tests/demo_json/general/mysql/json_access_operators.queries.ts +++ /dev/null @@ -1,128 +0,0 @@ -export type JsonFieldAccessParams = []; - -export interface IJsonFieldAccessResult { - activeJson: string; - ageJson: string; - id: number; - name: string; - usernameJson: string; -} - -export interface IJsonFieldAccessQuery { - params: JsonFieldAccessParams; - result: IJsonFieldAccessResult; -} - -export type JsonFieldAccessTextParams = []; - -export interface IJsonFieldAccessTextResult { - active: number; - age: number; - email: string; - id: number; - name: string; - username: string; -} - -export interface IJsonFieldAccessTextQuery { - params: JsonFieldAccessTextParams; - result: IJsonFieldAccessTextResult; -} - -export type JsonNestedAccessParams = []; - -export interface IJsonNestedAccessResult { - addressJson: string; - city: any; - cityJson: string; - id: number; - name: string; - street: any; - zipCode: any; -} - -export interface IJsonNestedAccessQuery { - params: JsonNestedAccessParams; - result: IJsonNestedAccessResult; -} - -export type JsonArrayAccessParams = []; - -export interface IJsonArrayAccessResult { - firstItemJson: string; - firstItemName: any; - firstItemPrice: number; - id: number; - itemsJson: string; - name: string; - secondItemJson: string; -} - -export interface IJsonArrayAccessQuery { - params: JsonArrayAccessParams; - result: IJsonArrayAccessResult; -} - -export type JsonPathAccessParams = []; - -export interface IJsonPathAccessResult { - firstItemJson: string; - firstItemName: any; - firstItemRarity: any; - id: number; - level: number; - levelJson: string; - name: string; -} - -export interface IJsonPathAccessQuery { - params: JsonPathAccessParams; - result: IJsonPathAccessResult; -} - -export type JsonDeepPathAccessParams = []; - -export interface IJsonDeepPathAccessResult { - darkMode: number; - dbHost: any; - dbHostJson: string; - dbPort: number; - emailNotifications: number; - id: number; - name: string; -} - -export interface IJsonDeepPathAccessQuery { - params: JsonDeepPathAccessParams; - result: IJsonDeepPathAccessResult; -} - -export type JsonFilterByFieldParams = []; - -export interface IJsonFilterByFieldResult { - email: string; - id: number; - name: string; - username: string; -} - -export interface IJsonFilterByFieldQuery { - params: JsonFilterByFieldParams; - result: IJsonFilterByFieldResult; -} - -export type JsonNullHandlingParams = []; - -export interface IJsonNullHandlingResult { - firstComment: any; - firstReviewer: any; - id: number; - secondComment: any; - thirdComment: any; - thirdReviewer: any; -} - -export interface IJsonNullHandlingQuery { - params: JsonNullHandlingParams; - result: IJsonNullHandlingResult; -} diff --git a/tests/demo_json/general/mysql/json_access_operators.snapshot.ts b/tests/demo_json/general/mysql/json_access_operators.snapshot.ts deleted file mode 100644 index c93b1dc7..00000000 --- a/tests/demo_json/general/mysql/json_access_operators.snapshot.ts +++ /dev/null @@ -1,129 +0,0 @@ -export type JsonFieldAccessParams = []; - -export interface IJsonFieldAccessResult { - activeJson: string; - ageJson: string; - id: number; - name: string; - usernameJson: string; -} - -export interface IJsonFieldAccessQuery { - params: JsonFieldAccessParams; - result: IJsonFieldAccessResult; -} - -export type JsonFieldAccessTextParams = []; - -export interface IJsonFieldAccessTextResult { - active: number; - age: number; - email: string; - id: number; - name: string; - username: string; -} - -export interface IJsonFieldAccessTextQuery { - params: JsonFieldAccessTextParams; - result: IJsonFieldAccessTextResult; -} - -export type JsonNestedAccessParams = []; - -export interface IJsonNestedAccessResult { - addressJson: string; - city: any; - cityJson: string; - id: number; - name: string; - street: any; - zipCode: any; -} - -export interface IJsonNestedAccessQuery { - params: JsonNestedAccessParams; - result: IJsonNestedAccessResult; -} - -export type JsonArrayAccessParams = []; - -export interface IJsonArrayAccessResult { - firstItemJson: string; - firstItemName: any; - firstItemPrice: number; - id: number; - itemsJson: string; - name: string; - secondItemJson: string; -} - -export interface IJsonArrayAccessQuery { - params: JsonArrayAccessParams; - result: IJsonArrayAccessResult; -} - -export type JsonPathAccessParams = []; - -export interface IJsonPathAccessResult { - firstItemJson: string; - firstItemName: any; - firstItemRarity: any; - id: number; - level: number; - levelJson: string; - name: string; -} - -export interface IJsonPathAccessQuery { - params: JsonPathAccessParams; - result: IJsonPathAccessResult; -} - -export type JsonDeepPathAccessParams = []; - -export interface IJsonDeepPathAccessResult { - darkMode: number; - dbHost: any; - dbHostJson: string; - dbPort: number; - emailNotifications: number; - id: number; - name: string; -} - -export interface IJsonDeepPathAccessQuery { - params: JsonDeepPathAccessParams; - result: IJsonDeepPathAccessResult; -} - -export type JsonFilterByFieldParams = []; - -export interface IJsonFilterByFieldResult { - email: string; - id: number; - name: string; - username: string; -} - -export interface IJsonFilterByFieldQuery { - params: JsonFilterByFieldParams; - result: IJsonFilterByFieldResult; -} - -export type JsonNullHandlingParams = []; - -export interface IJsonNullHandlingResult { - firstComment: any; - firstReviewer: any; - id: number; - secondComment: any; - thirdComment: any; - thirdReviewer: any; -} - -export interface IJsonNullHandlingQuery { - params: JsonNullHandlingParams; - result: IJsonNullHandlingResult; -} - diff --git a/tests/demo_json/general/mysql/json_array_functions.queries.ts b/tests/demo_json/general/mysql/json_array_functions.queries.ts deleted file mode 100644 index 6b057d4f..00000000 --- a/tests/demo_json/general/mysql/json_array_functions.queries.ts +++ /dev/null @@ -1,130 +0,0 @@ -export type JsonArrayLengthParams = []; - -export interface IJsonArrayLengthResult { - id: number; - itemsCount: any; - name: string; - tagsCount: any; -} - -export interface IJsonArrayLengthQuery { - params: JsonArrayLengthParams; - result: IJsonArrayLengthResult; -} - -export type JsonArrayExtractParams = []; - -export interface IJsonArrayExtractResult { - firstTag: any; - id: number; - name: string; - secondTag: any; - thirdTag: any; -} - -export interface IJsonArrayExtractQuery { - params: JsonArrayExtractParams; - result: IJsonArrayExtractResult; -} - -export type JsonArrayContainsParams = []; - -export interface IJsonArrayContainsResult { - hasDatabase: any; - hasMysql: any; - id: number; - name: string; - tags: any; -} - -export interface IJsonArrayContainsQuery { - params: JsonArrayContainsParams; - result: IJsonArrayContainsResult; -} - -export type JsonArrayMembershipParams = []; - -export interface IJsonArrayMembershipResult { - hasMysqlTag: any; - hasTutorialTag: any; - id: number; - name: string; -} - -export interface IJsonArrayMembershipQuery { - params: JsonArrayMembershipParams; - result: IJsonArrayMembershipResult; -} - -export type JsonNestedArrayAccessParams = []; - -export interface IJsonNestedArrayAccessResult { - firstItemName: any; - firstItemPrice: any; - id: number; - name: string; - secondItemName: any; - secondItemQuantity: any; -} - -export interface IJsonNestedArrayAccessQuery { - params: JsonNestedArrayAccessParams; - result: IJsonNestedArrayAccessResult; -} - -export type JsonDeepNestedArrayParams = []; - -export interface IJsonDeepNestedArrayResult { - firstAchievement: any; - firstInventoryItem: any; - firstItemRarity: any; - id: number; - name: string; - secondInventoryItem: any; -} - -export interface IJsonDeepNestedArrayQuery { - params: JsonDeepNestedArrayParams; - result: IJsonDeepNestedArrayResult; -} - -export type JsonArrayBuildParams = []; - -export interface IJsonArrayBuildResult { - firstTwoTags: any; - id: number; - name: string; -} - -export interface IJsonArrayBuildQuery { - params: JsonArrayBuildParams; - result: IJsonArrayBuildResult; -} - -export type JsonArrayAppendParams = []; - -export interface IJsonArrayAppendResult { - id: number; - name: string; - originalTags: any; - tagsWithNew: any; -} - -export interface IJsonArrayAppendQuery { - params: JsonArrayAppendParams; - result: IJsonArrayAppendResult; -} - -export type JsonArrayInsertParams = []; - -export interface IJsonArrayInsertResult { - id: number; - name: string; - originalTags: any; - tagsWithInsert: any; -} - -export interface IJsonArrayInsertQuery { - params: JsonArrayInsertParams; - result: IJsonArrayInsertResult; -} diff --git a/tests/demo_json/general/mysql/json_array_functions.snapshot.ts b/tests/demo_json/general/mysql/json_array_functions.snapshot.ts deleted file mode 100644 index 1b069362..00000000 --- a/tests/demo_json/general/mysql/json_array_functions.snapshot.ts +++ /dev/null @@ -1,131 +0,0 @@ -export type JsonArrayLengthParams = []; - -export interface IJsonArrayLengthResult { - id: number; - itemsCount: any; - name: string; - tagsCount: any; -} - -export interface IJsonArrayLengthQuery { - params: JsonArrayLengthParams; - result: IJsonArrayLengthResult; -} - -export type JsonArrayExtractParams = []; - -export interface IJsonArrayExtractResult { - firstTag: any; - id: number; - name: string; - secondTag: any; - thirdTag: any; -} - -export interface IJsonArrayExtractQuery { - params: JsonArrayExtractParams; - result: IJsonArrayExtractResult; -} - -export type JsonArrayContainsParams = []; - -export interface IJsonArrayContainsResult { - hasDatabase: any; - hasMysql: any; - id: number; - name: string; - tags: any; -} - -export interface IJsonArrayContainsQuery { - params: JsonArrayContainsParams; - result: IJsonArrayContainsResult; -} - -export type JsonArrayMembershipParams = []; - -export interface IJsonArrayMembershipResult { - hasMysqlTag: any; - hasTutorialTag: any; - id: number; - name: string; -} - -export interface IJsonArrayMembershipQuery { - params: JsonArrayMembershipParams; - result: IJsonArrayMembershipResult; -} - -export type JsonNestedArrayAccessParams = []; - -export interface IJsonNestedArrayAccessResult { - firstItemName: any; - firstItemPrice: any; - id: number; - name: string; - secondItemName: any; - secondItemQuantity: any; -} - -export interface IJsonNestedArrayAccessQuery { - params: JsonNestedArrayAccessParams; - result: IJsonNestedArrayAccessResult; -} - -export type JsonDeepNestedArrayParams = []; - -export interface IJsonDeepNestedArrayResult { - firstAchievement: any; - firstInventoryItem: any; - firstItemRarity: any; - id: number; - name: string; - secondInventoryItem: any; -} - -export interface IJsonDeepNestedArrayQuery { - params: JsonDeepNestedArrayParams; - result: IJsonDeepNestedArrayResult; -} - -export type JsonArrayBuildParams = []; - -export interface IJsonArrayBuildResult { - firstTwoTags: any; - id: number; - name: string; -} - -export interface IJsonArrayBuildQuery { - params: JsonArrayBuildParams; - result: IJsonArrayBuildResult; -} - -export type JsonArrayAppendParams = []; - -export interface IJsonArrayAppendResult { - id: number; - name: string; - originalTags: any; - tagsWithNew: any; -} - -export interface IJsonArrayAppendQuery { - params: JsonArrayAppendParams; - result: IJsonArrayAppendResult; -} - -export type JsonArrayInsertParams = []; - -export interface IJsonArrayInsertResult { - id: number; - name: string; - originalTags: any; - tagsWithInsert: any; -} - -export interface IJsonArrayInsertQuery { - params: JsonArrayInsertParams; - result: IJsonArrayInsertResult; -} - diff --git a/tests/demo_json/general/mysql/json_array_functions.ts b/tests/demo_json/general/mysql/json_array_functions.ts deleted file mode 100644 index bc5ddeb2..00000000 --- a/tests/demo_json/general/mysql/json_array_functions.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { sql } from 'sqlx-ts' - - -// Test JSON_LENGTH - get array length -const jsonArrayLength = sql` --- @db: db_mysql --- @name: json array length -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_LENGTH(data, '$.items') AS items_count, - JSON_LENGTH(data, '$.tags') AS tags_count -FROM json_test_data -WHERE json_test_data.name IN ('shopping_cart', 'tags') -` - -// Test JSON_EXTRACT with array index -const jsonArrayExtract = sql` --- @db: db_mysql --- @name: json array extract -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_EXTRACT(data, '$.tags[0]') AS first_tag, - JSON_EXTRACT(data, '$.tags[1]') AS second_tag, - JSON_EXTRACT(data, '$.tags[2]') AS third_tag -FROM json_test_data -WHERE json_test_data.name = 'tags' -` - -// Test array contains using JSON_CONTAINS -const jsonArrayContains = sql` --- @db: db_mysql --- @name: json array contains -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_EXTRACT(data, '$.tags') AS tags, - JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('mysql')) AS has_mysql, - JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('database')) AS has_database -FROM json_test_data -WHERE json_test_data.name = 'tags' -` - -// Test array element membership -const jsonArrayMembership = sql` --- @db: db_mysql --- @name: json array membership -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('mysql')) AS has_mysql_tag, - JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('tutorial')) AS has_tutorial_tag -FROM json_test_data -WHERE json_test_data.name = 'tags' -` - -// Test nested array access -const jsonNestedArrayAccess = sql` --- @db: db_mysql --- @name: json nested array access -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_EXTRACT(data, '$.items[0].name') AS first_item_name, - JSON_EXTRACT(data, '$.items[0].price') AS first_item_price, - JSON_EXTRACT(data, '$.items[1].name') AS second_item_name, - JSON_EXTRACT(data, '$.items[1].quantity') AS second_item_quantity -FROM json_test_data -WHERE json_test_data.name = 'shopping_cart' -` - -// Test deep nested array -const jsonDeepNestedArray = sql` --- @db: db_mysql --- @name: json deep nested array -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_EXTRACT(data, '$.stats.inventory[0].item') AS first_inventory_item, - JSON_EXTRACT(data, '$.stats.inventory[0].rarity') AS first_item_rarity, - JSON_EXTRACT(data, '$.stats.inventory[1].item') AS second_inventory_item, - JSON_EXTRACT(data, '$.stats.achievements[0]') AS first_achievement -FROM json_test_data -WHERE json_test_data.name = 'game_stats' -` - -// Test JSON_ARRAY to build arrays -const jsonArrayBuild = sql` --- @db: db_mysql --- @name: json array build -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_ARRAY( - JSON_EXTRACT(data, '$.tags[0]'), - JSON_EXTRACT(data, '$.tags[1]') - ) AS first_two_tags -FROM json_test_data -WHERE json_test_data.name = 'tags' -` - -// Test JSON_ARRAY_APPEND to add elements -const jsonArrayAppend = sql` --- @db: db_mysql --- @name: json array append -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_EXTRACT(data, '$.tags') AS original_tags, - JSON_ARRAY_APPEND(JSON_EXTRACT(data, '$.tags'), '$', 'new_tag') AS tags_with_new -FROM json_test_data -WHERE json_test_data.name = 'tags' -` - -// Test JSON_ARRAY_INSERT to insert elements -const jsonArrayInsert = sql` --- @db: db_mysql --- @name: json array insert -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_EXTRACT(data, '$.tags') AS original_tags, - JSON_ARRAY_INSERT(JSON_EXTRACT(data, '$.tags'), '$[1]', 'inserted_tag') AS tags_with_insert -FROM json_test_data -WHERE json_test_data.name = 'tags' -` diff --git a/tests/demo_json/general/mysql/json_comprehensive.queries.ts b/tests/demo_json/general/mysql/json_comprehensive.queries.ts deleted file mode 100644 index 1067842b..00000000 --- a/tests/demo_json/general/mysql/json_comprehensive.queries.ts +++ /dev/null @@ -1,175 +0,0 @@ -export type JsonExtractParams = []; - -export interface IJsonExtractResult { - age: number; - email: any; - id: number; - name: string; - username: any; -} - -export interface IJsonExtractQuery { - params: JsonExtractParams; - result: IJsonExtractResult; -} - -export type JsonExtractShorthandParams = []; - -export interface IJsonExtractShorthandResult { - email: string; - id: number; - name: string; - username: string; -} - -export interface IJsonExtractShorthandQuery { - params: JsonExtractShorthandParams; - result: IJsonExtractShorthandResult; -} - -export type JsonNestedPathParams = []; - -export interface IJsonNestedPathResult { - city: any; - id: number; - name: string; - zipCode: any; -} - -export interface IJsonNestedPathQuery { - params: JsonNestedPathParams; - result: IJsonNestedPathResult; -} - -export type JsonArrayIndexParams = []; - -export interface IJsonArrayIndexResult { - firstItemName: any; - firstItemPrice: number; - id: number; - name: string; -} - -export interface IJsonArrayIndexQuery { - params: JsonArrayIndexParams; - result: IJsonArrayIndexResult; -} - -export type JsonArrayLengthParams = []; - -export interface IJsonArrayLengthResult { - id: number; - name: string; - tagsCount: any; -} - -export interface IJsonArrayLengthQuery { - params: JsonArrayLengthParams; - result: IJsonArrayLengthResult; -} - -export type JsonTypeParams = []; - -export interface IJsonTypeResult { - ageType: any; - id: number; - tagsType: any; - usernameType: any; -} - -export interface IJsonTypeQuery { - params: JsonTypeParams; - result: IJsonTypeResult; -} - -export type JsonContainsParams = []; - -export interface IJsonContainsResult { - id: number; - isActive: any; - name: string; -} - -export interface IJsonContainsQuery { - params: JsonContainsParams; - result: IJsonContainsResult; -} - -export type JsonKeysParams = []; - -export interface IJsonKeysResult { - allKeys: any; - id: number; - name: string; -} - -export interface IJsonKeysQuery { - params: JsonKeysParams; - result: IJsonKeysResult; -} - -export type JsonObjectBuildParams = []; - -export interface IJsonObjectBuildResult { - id: number; - name: string; - userSummary: any; -} - -export interface IJsonObjectBuildQuery { - params: JsonObjectBuildParams; - result: IJsonObjectBuildResult; -} - -export type JsonFilterParams = []; - -export interface IJsonFilterResult { - id: number; - name: string; - username: any; -} - -export interface IJsonFilterQuery { - params: JsonFilterParams; - result: IJsonFilterResult; -} - -export type JsonDeepPathParams = []; - -export interface IJsonDeepPathResult { - appName: any; - dbHost: any; - dbPort: number; - id: number; -} - -export interface IJsonDeepPathQuery { - params: JsonDeepPathParams; - result: IJsonDeepPathResult; -} - -export type JsonValidParams = []; - -export interface IJsonValidResult { - id: number; - isValidJson: any; - name: string; -} - -export interface IJsonValidQuery { - params: JsonValidParams; - result: IJsonValidResult; -} - -export type JsonSearchParams = []; - -export interface IJsonSearchResult { - id: number; - name: string; - usernamePath: any; -} - -export interface IJsonSearchQuery { - params: JsonSearchParams; - result: IJsonSearchResult; -} diff --git a/tests/demo_json/general/mysql/json_comprehensive.snapshot.ts b/tests/demo_json/general/mysql/json_comprehensive.snapshot.ts deleted file mode 100644 index af43c197..00000000 --- a/tests/demo_json/general/mysql/json_comprehensive.snapshot.ts +++ /dev/null @@ -1,176 +0,0 @@ -export type JsonExtractParams = []; - -export interface IJsonExtractResult { - age: number; - email: any; - id: number; - name: string; - username: any; -} - -export interface IJsonExtractQuery { - params: JsonExtractParams; - result: IJsonExtractResult; -} - -export type JsonExtractShorthandParams = []; - -export interface IJsonExtractShorthandResult { - email: string; - id: number; - name: string; - username: string; -} - -export interface IJsonExtractShorthandQuery { - params: JsonExtractShorthandParams; - result: IJsonExtractShorthandResult; -} - -export type JsonNestedPathParams = []; - -export interface IJsonNestedPathResult { - city: any; - id: number; - name: string; - zipCode: any; -} - -export interface IJsonNestedPathQuery { - params: JsonNestedPathParams; - result: IJsonNestedPathResult; -} - -export type JsonArrayIndexParams = []; - -export interface IJsonArrayIndexResult { - firstItemName: any; - firstItemPrice: number; - id: number; - name: string; -} - -export interface IJsonArrayIndexQuery { - params: JsonArrayIndexParams; - result: IJsonArrayIndexResult; -} - -export type JsonArrayLengthParams = []; - -export interface IJsonArrayLengthResult { - id: number; - name: string; - tagsCount: any; -} - -export interface IJsonArrayLengthQuery { - params: JsonArrayLengthParams; - result: IJsonArrayLengthResult; -} - -export type JsonTypeParams = []; - -export interface IJsonTypeResult { - ageType: any; - id: number; - tagsType: any; - usernameType: any; -} - -export interface IJsonTypeQuery { - params: JsonTypeParams; - result: IJsonTypeResult; -} - -export type JsonContainsParams = []; - -export interface IJsonContainsResult { - id: number; - isActive: any; - name: string; -} - -export interface IJsonContainsQuery { - params: JsonContainsParams; - result: IJsonContainsResult; -} - -export type JsonKeysParams = []; - -export interface IJsonKeysResult { - allKeys: any; - id: number; - name: string; -} - -export interface IJsonKeysQuery { - params: JsonKeysParams; - result: IJsonKeysResult; -} - -export type JsonObjectBuildParams = []; - -export interface IJsonObjectBuildResult { - id: number; - name: string; - userSummary: any; -} - -export interface IJsonObjectBuildQuery { - params: JsonObjectBuildParams; - result: IJsonObjectBuildResult; -} - -export type JsonFilterParams = []; - -export interface IJsonFilterResult { - id: number; - name: string; - username: any; -} - -export interface IJsonFilterQuery { - params: JsonFilterParams; - result: IJsonFilterResult; -} - -export type JsonDeepPathParams = []; - -export interface IJsonDeepPathResult { - appName: any; - dbHost: any; - dbPort: number; - id: number; -} - -export interface IJsonDeepPathQuery { - params: JsonDeepPathParams; - result: IJsonDeepPathResult; -} - -export type JsonValidParams = []; - -export interface IJsonValidResult { - id: number; - isValidJson: any; - name: string; -} - -export interface IJsonValidQuery { - params: JsonValidParams; - result: IJsonValidResult; -} - -export type JsonSearchParams = []; - -export interface IJsonSearchResult { - id: number; - name: string; - usernamePath: any; -} - -export interface IJsonSearchQuery { - params: JsonSearchParams; - result: IJsonSearchResult; -} - diff --git a/tests/demo_json/general/mysql/json_comprehensive.ts b/tests/demo_json/general/mysql/json_comprehensive.ts deleted file mode 100644 index 86069e37..00000000 --- a/tests/demo_json/general/mysql/json_comprehensive.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { sql } from 'sqlx-ts' - - -// Test 1: JSON_EXTRACT with -> operator -const jsonExtract = sql` --- @db: db_mysql --- @name: json extract -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.username')) AS username, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.email')) AS email, - CAST(JSON_EXTRACT(data, '$.age') AS UNSIGNED) AS age -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test 2: JSON_EXTRACT with ->> operator (shorthand) -const jsonExtractShorthand = sql` --- @db: db_mysql --- @name: json extract shorthand -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data->>'$.username' AS username, - data->>'$.email' AS email -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test 3: Nested JSON path -const jsonNestedPath = sql` --- @db: db_mysql --- @name: json nested path -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.city')) AS city, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.zipCode')) AS zip_code -FROM json_test_data -WHERE json_test_data.name = 'user_with_address' -` - -// Test 4: JSON array index access -const jsonArrayIndex = sql` --- @db: db_mysql --- @name: json array index -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.items[0].name')) AS first_item_name, - CAST(JSON_EXTRACT(data, '$.items[0].price') AS DECIMAL(10,2)) AS first_item_price -FROM json_test_data -WHERE json_test_data.name = 'shopping_cart' -` - -// Test 5: JSON_LENGTH for array length -const jsonArrayLength = sql` --- @db: db_mysql --- @name: json array length -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_LENGTH(JSON_EXTRACT(data, '$.tags')) AS tags_count -FROM json_test_data -WHERE json_test_data.name = 'tags' -` - -// Test 6: JSON_TYPE -const jsonType = sql` --- @db: db_mysql --- @name: json type -SELECT - json_test_data.id AS id, - JSON_TYPE(JSON_EXTRACT(data, '$.username')) AS username_type, - JSON_TYPE(JSON_EXTRACT(data, '$.age')) AS age_type, - JSON_TYPE(JSON_EXTRACT(data, '$.tags')) AS tags_type -FROM json_test_data -WHERE json_test_data.name IN ('user_profile', 'tags') -` - -// Test 7: JSON_CONTAINS for containment check -const jsonContains = sql` --- @db: db_mysql --- @name: json contains -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_CONTAINS(data, 'true', '$.active') AS is_active -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test 8: JSON_KEYS to get object keys -const jsonKeys = sql` --- @db: db_mysql --- @name: json keys -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_KEYS(data) AS all_keys -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test 9: JSON_OBJECT to build objects with type inference -const jsonObjectBuild = sql` --- @db: db_mysql --- @name: json object build -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_OBJECT( - 'id', json_test_data.id, - 'name', json_test_data.name, - 'username', JSON_EXTRACT(data, '$.username'), - 'email', JSON_EXTRACT(data, '$.email') - ) AS user_summary -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test 10: Filter using JSON values -const jsonFilter = sql` --- @db: db_mysql --- @name: json filter -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.username')) AS username -FROM json_test_data -WHERE CAST(JSON_EXTRACT(data, '$.active') AS UNSIGNED) = 1 - AND CAST(JSON_EXTRACT(data, '$.age') AS UNSIGNED) > 25 -` - -// Test 11: Deep nested path -const jsonDeepPath = sql` --- @db: db_mysql --- @name: json deep path -SELECT - json_test_data.id AS id, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.app.name')) AS app_name, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.app.settings.database.host')) AS db_host, - CAST(JSON_EXTRACT(data, '$.app.settings.database.port') AS UNSIGNED) AS db_port -FROM json_test_data -WHERE json_test_data.name = 'nested_config' -` - -// Test 12: JSON_VALID -const jsonValid = sql` --- @db: db_mysql --- @name: json valid -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_VALID(data) AS is_valid_json -FROM json_test_data -LIMIT 3 -` - -// Test 13: JSON_SEARCH to find values -const jsonSearch = sql` --- @db: db_mysql --- @name: json search -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_SEARCH(data, 'one', 'john_doe') AS username_path -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` diff --git a/tests/demo_json/general/mysql/json_object_functions.queries.ts b/tests/demo_json/general/mysql/json_object_functions.queries.ts deleted file mode 100644 index 37a6c0f4..00000000 --- a/tests/demo_json/general/mysql/json_object_functions.queries.ts +++ /dev/null @@ -1,210 +0,0 @@ -export type JsonObjectKeysParams = []; - -export interface IJsonObjectKeysResult { - id: number; - name: string; - objectKeys: any; -} - -export interface IJsonObjectKeysQuery { - params: JsonObjectKeysParams; - result: IJsonObjectKeysResult; -} - -export type JsonObjectKeysPathParams = []; - -export interface IJsonObjectKeysPathResult { - addressKeys: any; - id: number; - name: string; -} - -export interface IJsonObjectKeysPathQuery { - params: JsonObjectKeysPathParams; - result: IJsonObjectKeysPathResult; -} - -export type JsonTypeofParams = []; - -export interface IJsonTypeofResult { - activeType: any; - ageType: any; - id: number; - itemsType: any; - name: string; - tagsType: any; - usernameType: any; -} - -export interface IJsonTypeofQuery { - params: JsonTypeofParams; - result: IJsonTypeofResult; -} - -export type JsonContainsParams = []; - -export interface IJsonContainsResult { - hasSpecificUsername: any; - id: number; - isActive: any; - name: string; -} - -export interface IJsonContainsQuery { - params: JsonContainsParams; - result: IJsonContainsResult; -} - -export type JsonContainsPathParams = []; - -export interface IJsonContainsPathResult { - hasAddress: any; - hasBoth: any; - hasNonexistent: any; - hasUsername: any; - id: number; - name: string; -} - -export interface IJsonContainsPathQuery { - params: JsonContainsPathParams; - result: IJsonContainsPathResult; -} - -export type JsonObjectBuildParams = []; - -export interface IJsonObjectBuildResult { - id: number; - name: string; - userSummary: any; -} - -export interface IJsonObjectBuildQuery { - params: JsonObjectBuildParams; - result: IJsonObjectBuildResult; -} - -export type JsonSetParams = []; - -export interface IJsonSetResult { - id: number; - name: string; - originalData: object; - updatedAge: any; - updatedCity: any; -} - -export interface IJsonSetQuery { - params: JsonSetParams; - result: IJsonSetResult; -} - -export type JsonInsertParams = []; - -export interface IJsonInsertResult { - id: number; - name: string; - originalData: object; - withPhone: any; -} - -export interface IJsonInsertQuery { - params: JsonInsertParams; - result: IJsonInsertResult; -} - -export type JsonReplaceParams = []; - -export interface IJsonReplaceResult { - id: number; - name: string; - originalData: object; - withNewUsername: any; -} - -export interface IJsonReplaceQuery { - params: JsonReplaceParams; - result: IJsonReplaceResult; -} - -export type JsonRemoveParams = []; - -export interface IJsonRemoveResult { - id: number; - name: string; - originalData: object; - withoutAge: any; -} - -export interface IJsonRemoveQuery { - params: JsonRemoveParams; - result: IJsonRemoveResult; -} - -export type JsonMergePatchParams = []; - -export interface IJsonMergePatchResult { - id: number; - mergedData: any; - name: string; - originalData: object; -} - -export interface IJsonMergePatchQuery { - params: JsonMergePatchParams; - result: IJsonMergePatchResult; -} - -export type JsonMergePreserveParams = []; - -export interface IJsonMergePreserveResult { - id: number; - mergedData: any; - name: string; - originalData: object; -} - -export interface IJsonMergePreserveQuery { - params: JsonMergePreserveParams; - result: IJsonMergePreserveResult; -} - -export type JsonSearchParams = []; - -export interface IJsonSearchResult { - emailPath: any; - id: number; - name: string; - usernamePath: any; -} - -export interface IJsonSearchQuery { - params: JsonSearchParams; - result: IJsonSearchResult; -} - -export type JsonDepthParams = []; - -export interface IJsonDepthResult { - dataDepth: any; - id: number; - name: string; -} - -export interface IJsonDepthQuery { - params: JsonDepthParams; - result: IJsonDepthResult; -} - -export type JsonValidParams = []; - -export interface IJsonValidResult { - id: number; - isValidJson: any; - name: string; -} - -export interface IJsonValidQuery { - params: JsonValidParams; - result: IJsonValidResult; -} diff --git a/tests/demo_json/general/mysql/json_object_functions.snapshot.ts b/tests/demo_json/general/mysql/json_object_functions.snapshot.ts deleted file mode 100644 index 35cb4bdf..00000000 --- a/tests/demo_json/general/mysql/json_object_functions.snapshot.ts +++ /dev/null @@ -1,211 +0,0 @@ -export type JsonObjectKeysParams = []; - -export interface IJsonObjectKeysResult { - id: number; - name: string; - objectKeys: any; -} - -export interface IJsonObjectKeysQuery { - params: JsonObjectKeysParams; - result: IJsonObjectKeysResult; -} - -export type JsonObjectKeysPathParams = []; - -export interface IJsonObjectKeysPathResult { - addressKeys: any; - id: number; - name: string; -} - -export interface IJsonObjectKeysPathQuery { - params: JsonObjectKeysPathParams; - result: IJsonObjectKeysPathResult; -} - -export type JsonTypeofParams = []; - -export interface IJsonTypeofResult { - activeType: any; - ageType: any; - id: number; - itemsType: any; - name: string; - tagsType: any; - usernameType: any; -} - -export interface IJsonTypeofQuery { - params: JsonTypeofParams; - result: IJsonTypeofResult; -} - -export type JsonContainsParams = []; - -export interface IJsonContainsResult { - hasSpecificUsername: any; - id: number; - isActive: any; - name: string; -} - -export interface IJsonContainsQuery { - params: JsonContainsParams; - result: IJsonContainsResult; -} - -export type JsonContainsPathParams = []; - -export interface IJsonContainsPathResult { - hasAddress: any; - hasBoth: any; - hasNonexistent: any; - hasUsername: any; - id: number; - name: string; -} - -export interface IJsonContainsPathQuery { - params: JsonContainsPathParams; - result: IJsonContainsPathResult; -} - -export type JsonObjectBuildParams = []; - -export interface IJsonObjectBuildResult { - id: number; - name: string; - userSummary: any; -} - -export interface IJsonObjectBuildQuery { - params: JsonObjectBuildParams; - result: IJsonObjectBuildResult; -} - -export type JsonSetParams = []; - -export interface IJsonSetResult { - id: number; - name: string; - originalData: object; - updatedAge: any; - updatedCity: any; -} - -export interface IJsonSetQuery { - params: JsonSetParams; - result: IJsonSetResult; -} - -export type JsonInsertParams = []; - -export interface IJsonInsertResult { - id: number; - name: string; - originalData: object; - withPhone: any; -} - -export interface IJsonInsertQuery { - params: JsonInsertParams; - result: IJsonInsertResult; -} - -export type JsonReplaceParams = []; - -export interface IJsonReplaceResult { - id: number; - name: string; - originalData: object; - withNewUsername: any; -} - -export interface IJsonReplaceQuery { - params: JsonReplaceParams; - result: IJsonReplaceResult; -} - -export type JsonRemoveParams = []; - -export interface IJsonRemoveResult { - id: number; - name: string; - originalData: object; - withoutAge: any; -} - -export interface IJsonRemoveQuery { - params: JsonRemoveParams; - result: IJsonRemoveResult; -} - -export type JsonMergePatchParams = []; - -export interface IJsonMergePatchResult { - id: number; - mergedData: any; - name: string; - originalData: object; -} - -export interface IJsonMergePatchQuery { - params: JsonMergePatchParams; - result: IJsonMergePatchResult; -} - -export type JsonMergePreserveParams = []; - -export interface IJsonMergePreserveResult { - id: number; - mergedData: any; - name: string; - originalData: object; -} - -export interface IJsonMergePreserveQuery { - params: JsonMergePreserveParams; - result: IJsonMergePreserveResult; -} - -export type JsonSearchParams = []; - -export interface IJsonSearchResult { - emailPath: any; - id: number; - name: string; - usernamePath: any; -} - -export interface IJsonSearchQuery { - params: JsonSearchParams; - result: IJsonSearchResult; -} - -export type JsonDepthParams = []; - -export interface IJsonDepthResult { - dataDepth: any; - id: number; - name: string; -} - -export interface IJsonDepthQuery { - params: JsonDepthParams; - result: IJsonDepthResult; -} - -export type JsonValidParams = []; - -export interface IJsonValidResult { - id: number; - isValidJson: any; - name: string; -} - -export interface IJsonValidQuery { - params: JsonValidParams; - result: IJsonValidResult; -} - diff --git a/tests/demo_json/general/mysql/json_object_functions.ts b/tests/demo_json/general/mysql/json_object_functions.ts deleted file mode 100644 index e1c9b920..00000000 --- a/tests/demo_json/general/mysql/json_object_functions.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { sql } from 'sqlx-ts' - - -// Test JSON_KEYS - get all keys from object -const jsonObjectKeys = sql` --- @db: db_mysql --- @name: json object keys -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_KEYS(data) AS object_keys -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_KEYS with path -const jsonObjectKeysPath = sql` --- @db: db_mysql --- @name: json object keys path -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_KEYS(data, '$.address') AS address_keys -FROM json_test_data -WHERE json_test_data.name = 'user_with_address' -` - -// Test JSON_TYPE - get type of JSON value -const jsonTypeof = sql` --- @db: db_mysql --- @name: json typeof -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_TYPE(JSON_EXTRACT(data, '$.username')) AS username_type, - JSON_TYPE(JSON_EXTRACT(data, '$.age')) AS age_type, - JSON_TYPE(JSON_EXTRACT(data, '$.active')) AS active_type, - JSON_TYPE(JSON_EXTRACT(data, '$.items')) AS items_type, - JSON_TYPE(JSON_EXTRACT(data, '$.tags')) AS tags_type -FROM json_test_data -WHERE json_test_data.name IN ('user_profile', 'shopping_cart', 'tags') -` - -// Test JSON_CONTAINS - check if JSON contains value -const jsonContains = sql` --- @db: db_mysql --- @name: json contains -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_CONTAINS(data, JSON_QUOTE('john_doe'), '$.username') AS has_specific_username, - JSON_CONTAINS(data, 'true', '$.active') AS is_active -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_CONTAINS_PATH - check if path exists -const jsonContainsPath = sql` --- @db: db_mysql --- @name: json contains path -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_CONTAINS_PATH(data, 'one', '$.username') AS has_username, - JSON_CONTAINS_PATH(data, 'one', '$.address') AS has_address, - JSON_CONTAINS_PATH(data, 'one', '$.nonexistent') AS has_nonexistent, - JSON_CONTAINS_PATH(data, 'all', '$.username', '$.email') AS has_both -FROM json_test_data -WHERE json_test_data.name IN ('user_profile', 'user_with_address') -` - -// Test JSON_OBJECT - build JSON objects -const jsonObjectBuild = sql` --- @db: db_mysql --- @name: json object build -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_OBJECT( - 'id', json_test_data.id, - 'name', json_test_data.name, - 'username', JSON_EXTRACT(data, '$.username'), - 'email', JSON_EXTRACT(data, '$.email') - ) AS user_summary -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_SET - update value in JSON -const jsonSet = sql` --- @db: db_mysql --- @name: json set -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data AS original_data, - JSON_SET(data, '$.age', 31) AS updated_age, - JSON_SET(data, '$.address.city', 'New York') AS updated_city -FROM json_test_data -WHERE json_test_data.name IN ('user_profile', 'user_with_address') -LIMIT 2 -` - -// Test JSON_INSERT - insert value into JSON -const jsonInsert = sql` --- @db: db_mysql --- @name: json insert -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data AS original_data, - JSON_INSERT(data, '$.phone', '555-1234') AS with_phone -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_REPLACE - replace existing value -const jsonReplace = sql` --- @db: db_mysql --- @name: json replace -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data AS original_data, - JSON_REPLACE(data, '$.username', 'new_username') AS with_new_username -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_REMOVE - remove keys from JSON -const jsonRemove = sql` --- @db: db_mysql --- @name: json remove -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data AS original_data, - JSON_REMOVE(data, '$.age') AS without_age -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_MERGE_PATCH - merge JSON objects -const jsonMergePatch = sql` --- @db: db_mysql --- @name: json merge patch -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data AS original_data, - JSON_MERGE_PATCH(data, JSON_OBJECT('verified', true, 'lastLogin', '2024-01-15')) AS merged_data -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_MERGE_PRESERVE - merge preserving all values -const jsonMergePreserve = sql` --- @db: db_mysql --- @name: json merge preserve -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data AS original_data, - JSON_MERGE_PRESERVE(data, JSON_OBJECT('newField', 'newValue')) AS merged_data -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_SEARCH - find values in JSON -const jsonSearch = sql` --- @db: db_mysql --- @name: json search -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_SEARCH(data, 'one', 'john_doe') AS username_path, - JSON_SEARCH(data, 'one', 'john@example.com') AS email_path -FROM json_test_data -WHERE json_test_data.name = 'user_profile' -` - -// Test JSON_DEPTH - get depth of JSON -const jsonDepth = sql` --- @db: db_mysql --- @db: db_mysql --- @name: json depth -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_DEPTH(data) AS data_depth -FROM json_test_data -WHERE json_test_data.name IN ('user_profile', 'nested_config') -` - -// Test JSON_VALID - validate JSON -const jsonValid = sql` --- @db: db_mysql --- @db: db_mysql --- @name: json valid -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - JSON_VALID(data) AS is_valid_json -FROM json_test_data -LIMIT 3 -` diff --git a/tests/demo_json/general/mysql/json_operations.snapshot.ts b/tests/demo_json/general/mysql/json_operations.snapshot.ts deleted file mode 100644 index c78e66b3..00000000 --- a/tests/demo_json/general/mysql/json_operations.snapshot.ts +++ /dev/null @@ -1,37 +0,0 @@ -export type JsonObjectBasicParams = []; - -export interface IJsonObjectBasicResult { - id: number; - itemJson: any; -} - -export interface IJsonObjectBasicQuery { - params: JsonObjectBasicParams; - result: IJsonObjectBasicResult; -} - -export type JsonArrayAggregationParams = []; - -export interface IJsonArrayAggregationResult { - items: any; - rarity: string | null; -} - -export interface IJsonArrayAggregationQuery { - params: JsonArrayAggregationParams; - result: IJsonArrayAggregationResult; -} - -export type JsonOperatorsSelectParams = []; - -export interface IJsonOperatorsSelectResult { - extractedName: any; - id: number; - name: string; -} - -export interface IJsonOperatorsSelectQuery { - params: JsonOperatorsSelectParams; - result: IJsonOperatorsSelectResult; -} - diff --git a/tests/demo_json/modern/mysql/json_access_operators.ts b/tests/demo_json/modern/mysql/json_access_operators.ts deleted file mode 100644 index d1130170..00000000 --- a/tests/demo_json/modern/mysql/json_access_operators.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { sql } from 'sqlx-ts' - -// Test nested field access with JSON_EXTRACT -const jsonNestedAccess = sql` --- @db: db_mysql --- @name: json nested access -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data -> '$.address' AS address_json, - data -> '$.address.city' AS city_json, - JSON_UNQUOTE(data -> '$.address.city') AS city, - JSON_UNQUOTE(data -> '$.address.zipCode') AS zip_code, - JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.street')) AS street -FROM json_test_data -WHERE json_test_data.name = 'user_with_address' -` - -// Test array element access by index -const jsonArrayAccess = sql` --- @db: db_mysql --- @name: json array access -SELECT - json_test_data.id AS id, - json_test_data.name AS name, - data -> '$.items' AS items_json, - data -> '$.items[0]' AS first_item_json, - data -> '$.items[1]' AS second_item_json, - JSON_UNQUOTE(data -> '$.items[0].name') AS first_item_name, - CAST(data -> '$.items[0].price' AS DECIMAL(10,2)) AS first_item_price -FROM json_test_data -WHERE json_test_data.name = 'shopping_cart' -` diff --git a/tests/demo_json/modern/mysql/json_operations.queries.ts b/tests/demo_json/modern/mysql/json_operations.queries.ts deleted file mode 100644 index 110fa793..00000000 --- a/tests/demo_json/modern/mysql/json_operations.queries.ts +++ /dev/null @@ -1,23 +0,0 @@ -export type JsonArrayAggregationParams = []; - -export interface IJsonArrayAggregationResult { - items: any; - rarity: string | null; -} - -export interface IJsonArrayAggregationQuery { - params: JsonArrayAggregationParams; - result: IJsonArrayAggregationResult; -} - -export type JsonObjectBasicParams = []; - -export interface IJsonObjectBasicResult { - id: number; - itemJson: any; -} - -export interface IJsonObjectBasicQuery { - params: JsonObjectBasicParams; - result: IJsonObjectBasicResult; -} diff --git a/tests/demo_json/modern/mysql/json_operations.ts b/tests/demo_json/modern/mysql/json_operations.ts deleted file mode 100644 index 9b6ecb4c..00000000 --- a/tests/demo_json/modern/mysql/json_operations.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { sql } from 'sqlx-ts' - - -// JSON_ARRAYAGG for aggregation - aggregate rows into JSON array -const jsonArrayAggregation = sql` --- @db: db_mysql --- @name: json array aggregation -SELECT - items.rarity AS rarity, - JSON_ARRAYAGG(JSON_OBJECT('id', items.id, 'name', items.name)) AS items -FROM items -GROUP BY items.rarity -` - -// JSON_OBJECT basic - build object from columns -const jsonObjectBasic = sql` --- @db: db_mysql --- @name: json object basic -SELECT - items.id AS id, - JSON_OBJECT('id', items.id, 'name', items.name, 'rarity', items.rarity) AS item_json -FROM items -` diff --git a/tests/demo_json/general/mysql/json_access_operators.ts b/tests/demo_json/mysql/json_access_operators.ts similarity index 100% rename from tests/demo_json/general/mysql/json_access_operators.ts rename to tests/demo_json/mysql/json_access_operators.ts diff --git a/tests/demo_json/general/mysql/json_operations.queries.ts b/tests/demo_json/mysql/json_operations.queries.ts similarity index 100% rename from tests/demo_json/general/mysql/json_operations.queries.ts rename to tests/demo_json/mysql/json_operations.queries.ts diff --git a/tests/demo_json/general/mysql/json_operations.ts b/tests/demo_json/mysql/json_operations.ts similarity index 100% rename from tests/demo_json/general/mysql/json_operations.ts rename to tests/demo_json/mysql/json_operations.ts diff --git a/tests/demo_json/general/postgres/array_operations.queries.ts b/tests/demo_json/postgres/array_operations.queries.ts similarity index 100% rename from tests/demo_json/general/postgres/array_operations.queries.ts rename to tests/demo_json/postgres/array_operations.queries.ts diff --git a/tests/demo_json/general/postgres/array_operations.snapshot.ts b/tests/demo_json/postgres/array_operations.snapshot.ts similarity index 100% rename from tests/demo_json/general/postgres/array_operations.snapshot.ts rename to tests/demo_json/postgres/array_operations.snapshot.ts diff --git a/tests/demo_json/general/postgres/array_operations.ts b/tests/demo_json/postgres/array_operations.ts similarity index 100% rename from tests/demo_json/general/postgres/array_operations.ts rename to tests/demo_json/postgres/array_operations.ts diff --git a/tests/demo_json/general/postgres/json_access_operators.queries.ts b/tests/demo_json/postgres/json_access_operators.queries.ts similarity index 100% rename from tests/demo_json/general/postgres/json_access_operators.queries.ts rename to tests/demo_json/postgres/json_access_operators.queries.ts diff --git a/tests/demo_json/general/postgres/json_access_operators.snapshot.ts b/tests/demo_json/postgres/json_access_operators.snapshot.ts similarity index 100% rename from tests/demo_json/general/postgres/json_access_operators.snapshot.ts rename to tests/demo_json/postgres/json_access_operators.snapshot.ts diff --git a/tests/demo_json/general/postgres/json_access_operators.ts b/tests/demo_json/postgres/json_access_operators.ts similarity index 100% rename from tests/demo_json/general/postgres/json_access_operators.ts rename to tests/demo_json/postgres/json_access_operators.ts diff --git a/tests/demo_json/general/postgres/json_array_functions.queries.ts b/tests/demo_json/postgres/json_array_functions.queries.ts similarity index 100% rename from tests/demo_json/general/postgres/json_array_functions.queries.ts rename to tests/demo_json/postgres/json_array_functions.queries.ts diff --git a/tests/demo_json/general/postgres/json_array_functions.snapshot.ts b/tests/demo_json/postgres/json_array_functions.snapshot.ts similarity index 100% rename from tests/demo_json/general/postgres/json_array_functions.snapshot.ts rename to tests/demo_json/postgres/json_array_functions.snapshot.ts diff --git a/tests/demo_json/general/postgres/json_array_functions.ts b/tests/demo_json/postgres/json_array_functions.ts similarity index 100% rename from tests/demo_json/general/postgres/json_array_functions.ts rename to tests/demo_json/postgres/json_array_functions.ts diff --git a/tests/demo_json/general/postgres/json_comprehensive.queries.ts b/tests/demo_json/postgres/json_comprehensive.queries.ts similarity index 100% rename from tests/demo_json/general/postgres/json_comprehensive.queries.ts rename to tests/demo_json/postgres/json_comprehensive.queries.ts diff --git a/tests/demo_json/general/postgres/json_comprehensive.snapshot.ts b/tests/demo_json/postgres/json_comprehensive.snapshot.ts similarity index 100% rename from tests/demo_json/general/postgres/json_comprehensive.snapshot.ts rename to tests/demo_json/postgres/json_comprehensive.snapshot.ts diff --git a/tests/demo_json/general/postgres/json_comprehensive.ts b/tests/demo_json/postgres/json_comprehensive.ts similarity index 100% rename from tests/demo_json/general/postgres/json_comprehensive.ts rename to tests/demo_json/postgres/json_comprehensive.ts diff --git a/tests/demo_json/general/postgres/json_object_functions.queries.ts b/tests/demo_json/postgres/json_object_functions.queries.ts similarity index 100% rename from tests/demo_json/general/postgres/json_object_functions.queries.ts rename to tests/demo_json/postgres/json_object_functions.queries.ts diff --git a/tests/demo_json/general/postgres/json_object_functions.snapshot.ts b/tests/demo_json/postgres/json_object_functions.snapshot.ts similarity index 100% rename from tests/demo_json/general/postgres/json_object_functions.snapshot.ts rename to tests/demo_json/postgres/json_object_functions.snapshot.ts diff --git a/tests/demo_json/general/postgres/json_object_functions.ts b/tests/demo_json/postgres/json_object_functions.ts similarity index 100% rename from tests/demo_json/general/postgres/json_object_functions.ts rename to tests/demo_json/postgres/json_object_functions.ts diff --git a/tests/demo_json/general/postgres/jsonb_operations.queries.ts b/tests/demo_json/postgres/jsonb_operations.queries.ts similarity index 100% rename from tests/demo_json/general/postgres/jsonb_operations.queries.ts rename to tests/demo_json/postgres/jsonb_operations.queries.ts diff --git a/tests/demo_json/general/postgres/jsonb_operations.snapshot.ts b/tests/demo_json/postgres/jsonb_operations.snapshot.ts similarity index 100% rename from tests/demo_json/general/postgres/jsonb_operations.snapshot.ts rename to tests/demo_json/postgres/jsonb_operations.snapshot.ts diff --git a/tests/demo_json/general/postgres/jsonb_operations.ts b/tests/demo_json/postgres/jsonb_operations.ts similarity index 100% rename from tests/demo_json/general/postgres/jsonb_operations.ts rename to tests/demo_json/postgres/jsonb_operations.ts diff --git a/tests/demo_json/general/postgres/upsert.queries.ts b/tests/demo_json/postgres/upsert.queries.ts similarity index 100% rename from tests/demo_json/general/postgres/upsert.queries.ts rename to tests/demo_json/postgres/upsert.queries.ts diff --git a/tests/demo_json/general/postgres/upsert.snapshot.ts b/tests/demo_json/postgres/upsert.snapshot.ts similarity index 100% rename from tests/demo_json/general/postgres/upsert.snapshot.ts rename to tests/demo_json/postgres/upsert.snapshot.ts diff --git a/tests/demo_json/general/postgres/upsert.ts b/tests/demo_json/postgres/upsert.ts similarity index 100% rename from tests/demo_json/general/postgres/upsert.ts rename to tests/demo_json/postgres/upsert.ts From e51d14e7d89013d667ea85e74257c2c66ed14b59 Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 15:45:49 +1100 Subject: [PATCH 14/19] fix --- tests/demo_happy_path.rs | 11 +- .../mysql/json_access_operators.queries.ts | 128 +++++++++++ .../mysql/json_access_operators.snapshot.ts | 129 +++++++++++ .../mysql/json_array_functions.queries.ts | 130 +++++++++++ .../mysql/json_array_functions.snapshot.ts | 131 +++++++++++ tests/demo_json/mysql/json_array_functions.ts | 127 +++++++++++ .../mysql/json_comprehensive.queries.ts | 175 +++++++++++++++ .../mysql/json_comprehensive.snapshot.ts | 176 +++++++++++++++ tests/demo_json/mysql/json_comprehensive.ts | 171 ++++++++++++++ .../mysql/json_object_functions.queries.ts | 210 +++++++++++++++++ .../mysql/json_object_functions.snapshot.ts | 211 ++++++++++++++++++ .../demo_json/mysql/json_object_functions.ts | 206 +++++++++++++++++ .../mysql/json_operations.snapshot.ts | 37 +++ 13 files changed, 1837 insertions(+), 5 deletions(-) create mode 100644 tests/demo_json/mysql/json_access_operators.queries.ts create mode 100644 tests/demo_json/mysql/json_access_operators.snapshot.ts create mode 100644 tests/demo_json/mysql/json_array_functions.queries.ts create mode 100644 tests/demo_json/mysql/json_array_functions.snapshot.ts create mode 100644 tests/demo_json/mysql/json_array_functions.ts create mode 100644 tests/demo_json/mysql/json_comprehensive.queries.ts create mode 100644 tests/demo_json/mysql/json_comprehensive.snapshot.ts create mode 100644 tests/demo_json/mysql/json_comprehensive.ts create mode 100644 tests/demo_json/mysql/json_object_functions.queries.ts create mode 100644 tests/demo_json/mysql/json_object_functions.snapshot.ts create mode 100644 tests/demo_json/mysql/json_object_functions.ts create mode 100644 tests/demo_json/mysql/json_operations.snapshot.ts diff --git a/tests/demo_happy_path.rs b/tests/demo_happy_path.rs index 00b9b03f..033f22a7 100644 --- a/tests/demo_happy_path.rs +++ b/tests/demo_happy_path.rs @@ -96,21 +96,22 @@ mod demo_happy_path_tests { #[test] - fn all_demo_json_general() -> Result<(), Box> { + fn all_demo_json_postgres() -> Result<(), Box> { + // PostgreSQL JSON tests - compatible with all PostgreSQL versions that support JSON let root_path = current_dir().unwrap(); - let demo_path = root_path.join("tests/demo_json/general"); + let demo_path = root_path.join("tests/demo_json/postgres"); run_demo_test(&demo_path) } - #[test] - fn all_demo_json_modern() -> Result<(), Box> { + fn all_demo_json_mysql() -> Result<(), Box> { + // MySQL 5.7+ and PostgreSQL JSON tests if env::var("MYSQL_VERSION").ok() == Some("5.6".to_string()) { return Ok(()); // Skip test for MySQL 5.6 which doesn't support JSON functions } let root_path = current_dir().unwrap(); - let demo_path = root_path.join("tests/demo_json/modern"); + let demo_path = root_path.join("tests/demo_json/mysql"); run_demo_test(&demo_path) } diff --git a/tests/demo_json/mysql/json_access_operators.queries.ts b/tests/demo_json/mysql/json_access_operators.queries.ts new file mode 100644 index 00000000..81a2595e --- /dev/null +++ b/tests/demo_json/mysql/json_access_operators.queries.ts @@ -0,0 +1,128 @@ +export type JsonFieldAccessParams = []; + +export interface IJsonFieldAccessResult { + activeJson: string; + ageJson: string; + id: number; + name: string; + usernameJson: string; +} + +export interface IJsonFieldAccessQuery { + params: JsonFieldAccessParams; + result: IJsonFieldAccessResult; +} + +export type JsonFieldAccessTextParams = []; + +export interface IJsonFieldAccessTextResult { + active: number; + age: number; + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFieldAccessTextQuery { + params: JsonFieldAccessTextParams; + result: IJsonFieldAccessTextResult; +} + +export type JsonNestedAccessParams = []; + +export interface IJsonNestedAccessResult { + addressJson: string; + city: any; + cityJson: string; + id: number; + name: string; + street: any; + zipCode: any; +} + +export interface IJsonNestedAccessQuery { + params: JsonNestedAccessParams; + result: IJsonNestedAccessResult; +} + +export type JsonArrayAccessParams = []; + +export interface IJsonArrayAccessResult { + firstItemJson: string; + firstItemName: any; + firstItemPrice: number; + id: number; + itemsJson: string; + name: string; + secondItemJson: string; +} + +export interface IJsonArrayAccessQuery { + params: JsonArrayAccessParams; + result: IJsonArrayAccessResult; +} + +export type JsonPathAccessParams = []; + +export interface IJsonPathAccessResult { + firstItemJson: string; + firstItemName: any; + firstItemRarity: any; + id: number; + level: number; + levelJson: string; + name: string; +} + +export interface IJsonPathAccessQuery { + params: JsonPathAccessParams; + result: IJsonPathAccessResult; +} + +export type JsonDeepPathAccessParams = []; + +export interface IJsonDeepPathAccessResult { + darkMode: number; + dbHost: any; + dbHostJson: string; + dbPort: number; + emailNotifications: number; + id: number; + name: string; +} + +export interface IJsonDeepPathAccessQuery { + params: JsonDeepPathAccessParams; + result: IJsonDeepPathAccessResult; +} + +export type JsonFilterByFieldParams = []; + +export interface IJsonFilterByFieldResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFilterByFieldQuery { + params: JsonFilterByFieldParams; + result: IJsonFilterByFieldResult; +} + +export type JsonNullHandlingParams = []; + +export interface IJsonNullHandlingResult { + firstComment: any; + firstReviewer: any; + id: number; + secondComment: any; + thirdComment: any; + thirdReviewer: any; +} + +export interface IJsonNullHandlingQuery { + params: JsonNullHandlingParams; + result: IJsonNullHandlingResult; +} diff --git a/tests/demo_json/mysql/json_access_operators.snapshot.ts b/tests/demo_json/mysql/json_access_operators.snapshot.ts new file mode 100644 index 00000000..c93b1dc7 --- /dev/null +++ b/tests/demo_json/mysql/json_access_operators.snapshot.ts @@ -0,0 +1,129 @@ +export type JsonFieldAccessParams = []; + +export interface IJsonFieldAccessResult { + activeJson: string; + ageJson: string; + id: number; + name: string; + usernameJson: string; +} + +export interface IJsonFieldAccessQuery { + params: JsonFieldAccessParams; + result: IJsonFieldAccessResult; +} + +export type JsonFieldAccessTextParams = []; + +export interface IJsonFieldAccessTextResult { + active: number; + age: number; + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFieldAccessTextQuery { + params: JsonFieldAccessTextParams; + result: IJsonFieldAccessTextResult; +} + +export type JsonNestedAccessParams = []; + +export interface IJsonNestedAccessResult { + addressJson: string; + city: any; + cityJson: string; + id: number; + name: string; + street: any; + zipCode: any; +} + +export interface IJsonNestedAccessQuery { + params: JsonNestedAccessParams; + result: IJsonNestedAccessResult; +} + +export type JsonArrayAccessParams = []; + +export interface IJsonArrayAccessResult { + firstItemJson: string; + firstItemName: any; + firstItemPrice: number; + id: number; + itemsJson: string; + name: string; + secondItemJson: string; +} + +export interface IJsonArrayAccessQuery { + params: JsonArrayAccessParams; + result: IJsonArrayAccessResult; +} + +export type JsonPathAccessParams = []; + +export interface IJsonPathAccessResult { + firstItemJson: string; + firstItemName: any; + firstItemRarity: any; + id: number; + level: number; + levelJson: string; + name: string; +} + +export interface IJsonPathAccessQuery { + params: JsonPathAccessParams; + result: IJsonPathAccessResult; +} + +export type JsonDeepPathAccessParams = []; + +export interface IJsonDeepPathAccessResult { + darkMode: number; + dbHost: any; + dbHostJson: string; + dbPort: number; + emailNotifications: number; + id: number; + name: string; +} + +export interface IJsonDeepPathAccessQuery { + params: JsonDeepPathAccessParams; + result: IJsonDeepPathAccessResult; +} + +export type JsonFilterByFieldParams = []; + +export interface IJsonFilterByFieldResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonFilterByFieldQuery { + params: JsonFilterByFieldParams; + result: IJsonFilterByFieldResult; +} + +export type JsonNullHandlingParams = []; + +export interface IJsonNullHandlingResult { + firstComment: any; + firstReviewer: any; + id: number; + secondComment: any; + thirdComment: any; + thirdReviewer: any; +} + +export interface IJsonNullHandlingQuery { + params: JsonNullHandlingParams; + result: IJsonNullHandlingResult; +} + diff --git a/tests/demo_json/mysql/json_array_functions.queries.ts b/tests/demo_json/mysql/json_array_functions.queries.ts new file mode 100644 index 00000000..6b057d4f --- /dev/null +++ b/tests/demo_json/mysql/json_array_functions.queries.ts @@ -0,0 +1,130 @@ +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + itemsCount: any; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonArrayExtractParams = []; + +export interface IJsonArrayExtractResult { + firstTag: any; + id: number; + name: string; + secondTag: any; + thirdTag: any; +} + +export interface IJsonArrayExtractQuery { + params: JsonArrayExtractParams; + result: IJsonArrayExtractResult; +} + +export type JsonArrayContainsParams = []; + +export interface IJsonArrayContainsResult { + hasDatabase: any; + hasMysql: any; + id: number; + name: string; + tags: any; +} + +export interface IJsonArrayContainsQuery { + params: JsonArrayContainsParams; + result: IJsonArrayContainsResult; +} + +export type JsonArrayMembershipParams = []; + +export interface IJsonArrayMembershipResult { + hasMysqlTag: any; + hasTutorialTag: any; + id: number; + name: string; +} + +export interface IJsonArrayMembershipQuery { + params: JsonArrayMembershipParams; + result: IJsonArrayMembershipResult; +} + +export type JsonNestedArrayAccessParams = []; + +export interface IJsonNestedArrayAccessResult { + firstItemName: any; + firstItemPrice: any; + id: number; + name: string; + secondItemName: any; + secondItemQuantity: any; +} + +export interface IJsonNestedArrayAccessQuery { + params: JsonNestedArrayAccessParams; + result: IJsonNestedArrayAccessResult; +} + +export type JsonDeepNestedArrayParams = []; + +export interface IJsonDeepNestedArrayResult { + firstAchievement: any; + firstInventoryItem: any; + firstItemRarity: any; + id: number; + name: string; + secondInventoryItem: any; +} + +export interface IJsonDeepNestedArrayQuery { + params: JsonDeepNestedArrayParams; + result: IJsonDeepNestedArrayResult; +} + +export type JsonArrayBuildParams = []; + +export interface IJsonArrayBuildResult { + firstTwoTags: any; + id: number; + name: string; +} + +export interface IJsonArrayBuildQuery { + params: JsonArrayBuildParams; + result: IJsonArrayBuildResult; +} + +export type JsonArrayAppendParams = []; + +export interface IJsonArrayAppendResult { + id: number; + name: string; + originalTags: any; + tagsWithNew: any; +} + +export interface IJsonArrayAppendQuery { + params: JsonArrayAppendParams; + result: IJsonArrayAppendResult; +} + +export type JsonArrayInsertParams = []; + +export interface IJsonArrayInsertResult { + id: number; + name: string; + originalTags: any; + tagsWithInsert: any; +} + +export interface IJsonArrayInsertQuery { + params: JsonArrayInsertParams; + result: IJsonArrayInsertResult; +} diff --git a/tests/demo_json/mysql/json_array_functions.snapshot.ts b/tests/demo_json/mysql/json_array_functions.snapshot.ts new file mode 100644 index 00000000..1b069362 --- /dev/null +++ b/tests/demo_json/mysql/json_array_functions.snapshot.ts @@ -0,0 +1,131 @@ +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + itemsCount: any; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonArrayExtractParams = []; + +export interface IJsonArrayExtractResult { + firstTag: any; + id: number; + name: string; + secondTag: any; + thirdTag: any; +} + +export interface IJsonArrayExtractQuery { + params: JsonArrayExtractParams; + result: IJsonArrayExtractResult; +} + +export type JsonArrayContainsParams = []; + +export interface IJsonArrayContainsResult { + hasDatabase: any; + hasMysql: any; + id: number; + name: string; + tags: any; +} + +export interface IJsonArrayContainsQuery { + params: JsonArrayContainsParams; + result: IJsonArrayContainsResult; +} + +export type JsonArrayMembershipParams = []; + +export interface IJsonArrayMembershipResult { + hasMysqlTag: any; + hasTutorialTag: any; + id: number; + name: string; +} + +export interface IJsonArrayMembershipQuery { + params: JsonArrayMembershipParams; + result: IJsonArrayMembershipResult; +} + +export type JsonNestedArrayAccessParams = []; + +export interface IJsonNestedArrayAccessResult { + firstItemName: any; + firstItemPrice: any; + id: number; + name: string; + secondItemName: any; + secondItemQuantity: any; +} + +export interface IJsonNestedArrayAccessQuery { + params: JsonNestedArrayAccessParams; + result: IJsonNestedArrayAccessResult; +} + +export type JsonDeepNestedArrayParams = []; + +export interface IJsonDeepNestedArrayResult { + firstAchievement: any; + firstInventoryItem: any; + firstItemRarity: any; + id: number; + name: string; + secondInventoryItem: any; +} + +export interface IJsonDeepNestedArrayQuery { + params: JsonDeepNestedArrayParams; + result: IJsonDeepNestedArrayResult; +} + +export type JsonArrayBuildParams = []; + +export interface IJsonArrayBuildResult { + firstTwoTags: any; + id: number; + name: string; +} + +export interface IJsonArrayBuildQuery { + params: JsonArrayBuildParams; + result: IJsonArrayBuildResult; +} + +export type JsonArrayAppendParams = []; + +export interface IJsonArrayAppendResult { + id: number; + name: string; + originalTags: any; + tagsWithNew: any; +} + +export interface IJsonArrayAppendQuery { + params: JsonArrayAppendParams; + result: IJsonArrayAppendResult; +} + +export type JsonArrayInsertParams = []; + +export interface IJsonArrayInsertResult { + id: number; + name: string; + originalTags: any; + tagsWithInsert: any; +} + +export interface IJsonArrayInsertQuery { + params: JsonArrayInsertParams; + result: IJsonArrayInsertResult; +} + diff --git a/tests/demo_json/mysql/json_array_functions.ts b/tests/demo_json/mysql/json_array_functions.ts new file mode 100644 index 00000000..bc5ddeb2 --- /dev/null +++ b/tests/demo_json/mysql/json_array_functions.ts @@ -0,0 +1,127 @@ +import { sql } from 'sqlx-ts' + + +// Test JSON_LENGTH - get array length +const jsonArrayLength = sql` +-- @db: db_mysql +-- @name: json array length +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_LENGTH(data, '$.items') AS items_count, + JSON_LENGTH(data, '$.tags') AS tags_count +FROM json_test_data +WHERE json_test_data.name IN ('shopping_cart', 'tags') +` + +// Test JSON_EXTRACT with array index +const jsonArrayExtract = sql` +-- @db: db_mysql +-- @name: json array extract +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.tags[0]') AS first_tag, + JSON_EXTRACT(data, '$.tags[1]') AS second_tag, + JSON_EXTRACT(data, '$.tags[2]') AS third_tag +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test array contains using JSON_CONTAINS +const jsonArrayContains = sql` +-- @db: db_mysql +-- @name: json array contains +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.tags') AS tags, + JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('mysql')) AS has_mysql, + JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('database')) AS has_database +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test array element membership +const jsonArrayMembership = sql` +-- @db: db_mysql +-- @name: json array membership +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('mysql')) AS has_mysql_tag, + JSON_CONTAINS(JSON_EXTRACT(data, '$.tags'), JSON_QUOTE('tutorial')) AS has_tutorial_tag +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test nested array access +const jsonNestedArrayAccess = sql` +-- @db: db_mysql +-- @name: json nested array access +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.items[0].name') AS first_item_name, + JSON_EXTRACT(data, '$.items[0].price') AS first_item_price, + JSON_EXTRACT(data, '$.items[1].name') AS second_item_name, + JSON_EXTRACT(data, '$.items[1].quantity') AS second_item_quantity +FROM json_test_data +WHERE json_test_data.name = 'shopping_cart' +` + +// Test deep nested array +const jsonDeepNestedArray = sql` +-- @db: db_mysql +-- @name: json deep nested array +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.stats.inventory[0].item') AS first_inventory_item, + JSON_EXTRACT(data, '$.stats.inventory[0].rarity') AS first_item_rarity, + JSON_EXTRACT(data, '$.stats.inventory[1].item') AS second_inventory_item, + JSON_EXTRACT(data, '$.stats.achievements[0]') AS first_achievement +FROM json_test_data +WHERE json_test_data.name = 'game_stats' +` + +// Test JSON_ARRAY to build arrays +const jsonArrayBuild = sql` +-- @db: db_mysql +-- @name: json array build +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_ARRAY( + JSON_EXTRACT(data, '$.tags[0]'), + JSON_EXTRACT(data, '$.tags[1]') + ) AS first_two_tags +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test JSON_ARRAY_APPEND to add elements +const jsonArrayAppend = sql` +-- @db: db_mysql +-- @name: json array append +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.tags') AS original_tags, + JSON_ARRAY_APPEND(JSON_EXTRACT(data, '$.tags'), '$', 'new_tag') AS tags_with_new +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test JSON_ARRAY_INSERT to insert elements +const jsonArrayInsert = sql` +-- @db: db_mysql +-- @name: json array insert +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_EXTRACT(data, '$.tags') AS original_tags, + JSON_ARRAY_INSERT(JSON_EXTRACT(data, '$.tags'), '$[1]', 'inserted_tag') AS tags_with_insert +FROM json_test_data +WHERE json_test_data.name = 'tags' +` diff --git a/tests/demo_json/mysql/json_comprehensive.queries.ts b/tests/demo_json/mysql/json_comprehensive.queries.ts new file mode 100644 index 00000000..1067842b --- /dev/null +++ b/tests/demo_json/mysql/json_comprehensive.queries.ts @@ -0,0 +1,175 @@ +export type JsonExtractParams = []; + +export interface IJsonExtractResult { + age: number; + email: any; + id: number; + name: string; + username: any; +} + +export interface IJsonExtractQuery { + params: JsonExtractParams; + result: IJsonExtractResult; +} + +export type JsonExtractShorthandParams = []; + +export interface IJsonExtractShorthandResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonExtractShorthandQuery { + params: JsonExtractShorthandParams; + result: IJsonExtractShorthandResult; +} + +export type JsonNestedPathParams = []; + +export interface IJsonNestedPathResult { + city: any; + id: number; + name: string; + zipCode: any; +} + +export interface IJsonNestedPathQuery { + params: JsonNestedPathParams; + result: IJsonNestedPathResult; +} + +export type JsonArrayIndexParams = []; + +export interface IJsonArrayIndexResult { + firstItemName: any; + firstItemPrice: number; + id: number; + name: string; +} + +export interface IJsonArrayIndexQuery { + params: JsonArrayIndexParams; + result: IJsonArrayIndexResult; +} + +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonTypeParams = []; + +export interface IJsonTypeResult { + ageType: any; + id: number; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeQuery { + params: JsonTypeParams; + result: IJsonTypeResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + id: number; + isActive: any; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonKeysParams = []; + +export interface IJsonKeysResult { + allKeys: any; + id: number; + name: string; +} + +export interface IJsonKeysQuery { + params: JsonKeysParams; + result: IJsonKeysResult; +} + +export type JsonObjectBuildParams = []; + +export interface IJsonObjectBuildResult { + id: number; + name: string; + userSummary: any; +} + +export interface IJsonObjectBuildQuery { + params: JsonObjectBuildParams; + result: IJsonObjectBuildResult; +} + +export type JsonFilterParams = []; + +export interface IJsonFilterResult { + id: number; + name: string; + username: any; +} + +export interface IJsonFilterQuery { + params: JsonFilterParams; + result: IJsonFilterResult; +} + +export type JsonDeepPathParams = []; + +export interface IJsonDeepPathResult { + appName: any; + dbHost: any; + dbPort: number; + id: number; +} + +export interface IJsonDeepPathQuery { + params: JsonDeepPathParams; + result: IJsonDeepPathResult; +} + +export type JsonValidParams = []; + +export interface IJsonValidResult { + id: number; + isValidJson: any; + name: string; +} + +export interface IJsonValidQuery { + params: JsonValidParams; + result: IJsonValidResult; +} + +export type JsonSearchParams = []; + +export interface IJsonSearchResult { + id: number; + name: string; + usernamePath: any; +} + +export interface IJsonSearchQuery { + params: JsonSearchParams; + result: IJsonSearchResult; +} diff --git a/tests/demo_json/mysql/json_comprehensive.snapshot.ts b/tests/demo_json/mysql/json_comprehensive.snapshot.ts new file mode 100644 index 00000000..af43c197 --- /dev/null +++ b/tests/demo_json/mysql/json_comprehensive.snapshot.ts @@ -0,0 +1,176 @@ +export type JsonExtractParams = []; + +export interface IJsonExtractResult { + age: number; + email: any; + id: number; + name: string; + username: any; +} + +export interface IJsonExtractQuery { + params: JsonExtractParams; + result: IJsonExtractResult; +} + +export type JsonExtractShorthandParams = []; + +export interface IJsonExtractShorthandResult { + email: string; + id: number; + name: string; + username: string; +} + +export interface IJsonExtractShorthandQuery { + params: JsonExtractShorthandParams; + result: IJsonExtractShorthandResult; +} + +export type JsonNestedPathParams = []; + +export interface IJsonNestedPathResult { + city: any; + id: number; + name: string; + zipCode: any; +} + +export interface IJsonNestedPathQuery { + params: JsonNestedPathParams; + result: IJsonNestedPathResult; +} + +export type JsonArrayIndexParams = []; + +export interface IJsonArrayIndexResult { + firstItemName: any; + firstItemPrice: number; + id: number; + name: string; +} + +export interface IJsonArrayIndexQuery { + params: JsonArrayIndexParams; + result: IJsonArrayIndexResult; +} + +export type JsonArrayLengthParams = []; + +export interface IJsonArrayLengthResult { + id: number; + name: string; + tagsCount: any; +} + +export interface IJsonArrayLengthQuery { + params: JsonArrayLengthParams; + result: IJsonArrayLengthResult; +} + +export type JsonTypeParams = []; + +export interface IJsonTypeResult { + ageType: any; + id: number; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeQuery { + params: JsonTypeParams; + result: IJsonTypeResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + id: number; + isActive: any; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonKeysParams = []; + +export interface IJsonKeysResult { + allKeys: any; + id: number; + name: string; +} + +export interface IJsonKeysQuery { + params: JsonKeysParams; + result: IJsonKeysResult; +} + +export type JsonObjectBuildParams = []; + +export interface IJsonObjectBuildResult { + id: number; + name: string; + userSummary: any; +} + +export interface IJsonObjectBuildQuery { + params: JsonObjectBuildParams; + result: IJsonObjectBuildResult; +} + +export type JsonFilterParams = []; + +export interface IJsonFilterResult { + id: number; + name: string; + username: any; +} + +export interface IJsonFilterQuery { + params: JsonFilterParams; + result: IJsonFilterResult; +} + +export type JsonDeepPathParams = []; + +export interface IJsonDeepPathResult { + appName: any; + dbHost: any; + dbPort: number; + id: number; +} + +export interface IJsonDeepPathQuery { + params: JsonDeepPathParams; + result: IJsonDeepPathResult; +} + +export type JsonValidParams = []; + +export interface IJsonValidResult { + id: number; + isValidJson: any; + name: string; +} + +export interface IJsonValidQuery { + params: JsonValidParams; + result: IJsonValidResult; +} + +export type JsonSearchParams = []; + +export interface IJsonSearchResult { + id: number; + name: string; + usernamePath: any; +} + +export interface IJsonSearchQuery { + params: JsonSearchParams; + result: IJsonSearchResult; +} + diff --git a/tests/demo_json/mysql/json_comprehensive.ts b/tests/demo_json/mysql/json_comprehensive.ts new file mode 100644 index 00000000..86069e37 --- /dev/null +++ b/tests/demo_json/mysql/json_comprehensive.ts @@ -0,0 +1,171 @@ +import { sql } from 'sqlx-ts' + + +// Test 1: JSON_EXTRACT with -> operator +const jsonExtract = sql` +-- @db: db_mysql +-- @name: json extract +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.username')) AS username, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.email')) AS email, + CAST(JSON_EXTRACT(data, '$.age') AS UNSIGNED) AS age +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 2: JSON_EXTRACT with ->> operator (shorthand) +const jsonExtractShorthand = sql` +-- @db: db_mysql +-- @name: json extract shorthand +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data->>'$.username' AS username, + data->>'$.email' AS email +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 3: Nested JSON path +const jsonNestedPath = sql` +-- @db: db_mysql +-- @name: json nested path +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.city')) AS city, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.address.zipCode')) AS zip_code +FROM json_test_data +WHERE json_test_data.name = 'user_with_address' +` + +// Test 4: JSON array index access +const jsonArrayIndex = sql` +-- @db: db_mysql +-- @name: json array index +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.items[0].name')) AS first_item_name, + CAST(JSON_EXTRACT(data, '$.items[0].price') AS DECIMAL(10,2)) AS first_item_price +FROM json_test_data +WHERE json_test_data.name = 'shopping_cart' +` + +// Test 5: JSON_LENGTH for array length +const jsonArrayLength = sql` +-- @db: db_mysql +-- @name: json array length +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_LENGTH(JSON_EXTRACT(data, '$.tags')) AS tags_count +FROM json_test_data +WHERE json_test_data.name = 'tags' +` + +// Test 6: JSON_TYPE +const jsonType = sql` +-- @db: db_mysql +-- @name: json type +SELECT + json_test_data.id AS id, + JSON_TYPE(JSON_EXTRACT(data, '$.username')) AS username_type, + JSON_TYPE(JSON_EXTRACT(data, '$.age')) AS age_type, + JSON_TYPE(JSON_EXTRACT(data, '$.tags')) AS tags_type +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'tags') +` + +// Test 7: JSON_CONTAINS for containment check +const jsonContains = sql` +-- @db: db_mysql +-- @name: json contains +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_CONTAINS(data, 'true', '$.active') AS is_active +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 8: JSON_KEYS to get object keys +const jsonKeys = sql` +-- @db: db_mysql +-- @name: json keys +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_KEYS(data) AS all_keys +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 9: JSON_OBJECT to build objects with type inference +const jsonObjectBuild = sql` +-- @db: db_mysql +-- @name: json object build +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_OBJECT( + 'id', json_test_data.id, + 'name', json_test_data.name, + 'username', JSON_EXTRACT(data, '$.username'), + 'email', JSON_EXTRACT(data, '$.email') + ) AS user_summary +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test 10: Filter using JSON values +const jsonFilter = sql` +-- @db: db_mysql +-- @name: json filter +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.username')) AS username +FROM json_test_data +WHERE CAST(JSON_EXTRACT(data, '$.active') AS UNSIGNED) = 1 + AND CAST(JSON_EXTRACT(data, '$.age') AS UNSIGNED) > 25 +` + +// Test 11: Deep nested path +const jsonDeepPath = sql` +-- @db: db_mysql +-- @name: json deep path +SELECT + json_test_data.id AS id, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.app.name')) AS app_name, + JSON_UNQUOTE(JSON_EXTRACT(data, '$.app.settings.database.host')) AS db_host, + CAST(JSON_EXTRACT(data, '$.app.settings.database.port') AS UNSIGNED) AS db_port +FROM json_test_data +WHERE json_test_data.name = 'nested_config' +` + +// Test 12: JSON_VALID +const jsonValid = sql` +-- @db: db_mysql +-- @name: json valid +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_VALID(data) AS is_valid_json +FROM json_test_data +LIMIT 3 +` + +// Test 13: JSON_SEARCH to find values +const jsonSearch = sql` +-- @db: db_mysql +-- @name: json search +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_SEARCH(data, 'one', 'john_doe') AS username_path +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` diff --git a/tests/demo_json/mysql/json_object_functions.queries.ts b/tests/demo_json/mysql/json_object_functions.queries.ts new file mode 100644 index 00000000..37a6c0f4 --- /dev/null +++ b/tests/demo_json/mysql/json_object_functions.queries.ts @@ -0,0 +1,210 @@ +export type JsonObjectKeysParams = []; + +export interface IJsonObjectKeysResult { + id: number; + name: string; + objectKeys: any; +} + +export interface IJsonObjectKeysQuery { + params: JsonObjectKeysParams; + result: IJsonObjectKeysResult; +} + +export type JsonObjectKeysPathParams = []; + +export interface IJsonObjectKeysPathResult { + addressKeys: any; + id: number; + name: string; +} + +export interface IJsonObjectKeysPathQuery { + params: JsonObjectKeysPathParams; + result: IJsonObjectKeysPathResult; +} + +export type JsonTypeofParams = []; + +export interface IJsonTypeofResult { + activeType: any; + ageType: any; + id: number; + itemsType: any; + name: string; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeofQuery { + params: JsonTypeofParams; + result: IJsonTypeofResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + hasSpecificUsername: any; + id: number; + isActive: any; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonContainsPathParams = []; + +export interface IJsonContainsPathResult { + hasAddress: any; + hasBoth: any; + hasNonexistent: any; + hasUsername: any; + id: number; + name: string; +} + +export interface IJsonContainsPathQuery { + params: JsonContainsPathParams; + result: IJsonContainsPathResult; +} + +export type JsonObjectBuildParams = []; + +export interface IJsonObjectBuildResult { + id: number; + name: string; + userSummary: any; +} + +export interface IJsonObjectBuildQuery { + params: JsonObjectBuildParams; + result: IJsonObjectBuildResult; +} + +export type JsonSetParams = []; + +export interface IJsonSetResult { + id: number; + name: string; + originalData: object; + updatedAge: any; + updatedCity: any; +} + +export interface IJsonSetQuery { + params: JsonSetParams; + result: IJsonSetResult; +} + +export type JsonInsertParams = []; + +export interface IJsonInsertResult { + id: number; + name: string; + originalData: object; + withPhone: any; +} + +export interface IJsonInsertQuery { + params: JsonInsertParams; + result: IJsonInsertResult; +} + +export type JsonReplaceParams = []; + +export interface IJsonReplaceResult { + id: number; + name: string; + originalData: object; + withNewUsername: any; +} + +export interface IJsonReplaceQuery { + params: JsonReplaceParams; + result: IJsonReplaceResult; +} + +export type JsonRemoveParams = []; + +export interface IJsonRemoveResult { + id: number; + name: string; + originalData: object; + withoutAge: any; +} + +export interface IJsonRemoveQuery { + params: JsonRemoveParams; + result: IJsonRemoveResult; +} + +export type JsonMergePatchParams = []; + +export interface IJsonMergePatchResult { + id: number; + mergedData: any; + name: string; + originalData: object; +} + +export interface IJsonMergePatchQuery { + params: JsonMergePatchParams; + result: IJsonMergePatchResult; +} + +export type JsonMergePreserveParams = []; + +export interface IJsonMergePreserveResult { + id: number; + mergedData: any; + name: string; + originalData: object; +} + +export interface IJsonMergePreserveQuery { + params: JsonMergePreserveParams; + result: IJsonMergePreserveResult; +} + +export type JsonSearchParams = []; + +export interface IJsonSearchResult { + emailPath: any; + id: number; + name: string; + usernamePath: any; +} + +export interface IJsonSearchQuery { + params: JsonSearchParams; + result: IJsonSearchResult; +} + +export type JsonDepthParams = []; + +export interface IJsonDepthResult { + dataDepth: any; + id: number; + name: string; +} + +export interface IJsonDepthQuery { + params: JsonDepthParams; + result: IJsonDepthResult; +} + +export type JsonValidParams = []; + +export interface IJsonValidResult { + id: number; + isValidJson: any; + name: string; +} + +export interface IJsonValidQuery { + params: JsonValidParams; + result: IJsonValidResult; +} diff --git a/tests/demo_json/mysql/json_object_functions.snapshot.ts b/tests/demo_json/mysql/json_object_functions.snapshot.ts new file mode 100644 index 00000000..35cb4bdf --- /dev/null +++ b/tests/demo_json/mysql/json_object_functions.snapshot.ts @@ -0,0 +1,211 @@ +export type JsonObjectKeysParams = []; + +export interface IJsonObjectKeysResult { + id: number; + name: string; + objectKeys: any; +} + +export interface IJsonObjectKeysQuery { + params: JsonObjectKeysParams; + result: IJsonObjectKeysResult; +} + +export type JsonObjectKeysPathParams = []; + +export interface IJsonObjectKeysPathResult { + addressKeys: any; + id: number; + name: string; +} + +export interface IJsonObjectKeysPathQuery { + params: JsonObjectKeysPathParams; + result: IJsonObjectKeysPathResult; +} + +export type JsonTypeofParams = []; + +export interface IJsonTypeofResult { + activeType: any; + ageType: any; + id: number; + itemsType: any; + name: string; + tagsType: any; + usernameType: any; +} + +export interface IJsonTypeofQuery { + params: JsonTypeofParams; + result: IJsonTypeofResult; +} + +export type JsonContainsParams = []; + +export interface IJsonContainsResult { + hasSpecificUsername: any; + id: number; + isActive: any; + name: string; +} + +export interface IJsonContainsQuery { + params: JsonContainsParams; + result: IJsonContainsResult; +} + +export type JsonContainsPathParams = []; + +export interface IJsonContainsPathResult { + hasAddress: any; + hasBoth: any; + hasNonexistent: any; + hasUsername: any; + id: number; + name: string; +} + +export interface IJsonContainsPathQuery { + params: JsonContainsPathParams; + result: IJsonContainsPathResult; +} + +export type JsonObjectBuildParams = []; + +export interface IJsonObjectBuildResult { + id: number; + name: string; + userSummary: any; +} + +export interface IJsonObjectBuildQuery { + params: JsonObjectBuildParams; + result: IJsonObjectBuildResult; +} + +export type JsonSetParams = []; + +export interface IJsonSetResult { + id: number; + name: string; + originalData: object; + updatedAge: any; + updatedCity: any; +} + +export interface IJsonSetQuery { + params: JsonSetParams; + result: IJsonSetResult; +} + +export type JsonInsertParams = []; + +export interface IJsonInsertResult { + id: number; + name: string; + originalData: object; + withPhone: any; +} + +export interface IJsonInsertQuery { + params: JsonInsertParams; + result: IJsonInsertResult; +} + +export type JsonReplaceParams = []; + +export interface IJsonReplaceResult { + id: number; + name: string; + originalData: object; + withNewUsername: any; +} + +export interface IJsonReplaceQuery { + params: JsonReplaceParams; + result: IJsonReplaceResult; +} + +export type JsonRemoveParams = []; + +export interface IJsonRemoveResult { + id: number; + name: string; + originalData: object; + withoutAge: any; +} + +export interface IJsonRemoveQuery { + params: JsonRemoveParams; + result: IJsonRemoveResult; +} + +export type JsonMergePatchParams = []; + +export interface IJsonMergePatchResult { + id: number; + mergedData: any; + name: string; + originalData: object; +} + +export interface IJsonMergePatchQuery { + params: JsonMergePatchParams; + result: IJsonMergePatchResult; +} + +export type JsonMergePreserveParams = []; + +export interface IJsonMergePreserveResult { + id: number; + mergedData: any; + name: string; + originalData: object; +} + +export interface IJsonMergePreserveQuery { + params: JsonMergePreserveParams; + result: IJsonMergePreserveResult; +} + +export type JsonSearchParams = []; + +export interface IJsonSearchResult { + emailPath: any; + id: number; + name: string; + usernamePath: any; +} + +export interface IJsonSearchQuery { + params: JsonSearchParams; + result: IJsonSearchResult; +} + +export type JsonDepthParams = []; + +export interface IJsonDepthResult { + dataDepth: any; + id: number; + name: string; +} + +export interface IJsonDepthQuery { + params: JsonDepthParams; + result: IJsonDepthResult; +} + +export type JsonValidParams = []; + +export interface IJsonValidResult { + id: number; + isValidJson: any; + name: string; +} + +export interface IJsonValidQuery { + params: JsonValidParams; + result: IJsonValidResult; +} + diff --git a/tests/demo_json/mysql/json_object_functions.ts b/tests/demo_json/mysql/json_object_functions.ts new file mode 100644 index 00000000..e1c9b920 --- /dev/null +++ b/tests/demo_json/mysql/json_object_functions.ts @@ -0,0 +1,206 @@ +import { sql } from 'sqlx-ts' + + +// Test JSON_KEYS - get all keys from object +const jsonObjectKeys = sql` +-- @db: db_mysql +-- @name: json object keys +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_KEYS(data) AS object_keys +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_KEYS with path +const jsonObjectKeysPath = sql` +-- @db: db_mysql +-- @name: json object keys path +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_KEYS(data, '$.address') AS address_keys +FROM json_test_data +WHERE json_test_data.name = 'user_with_address' +` + +// Test JSON_TYPE - get type of JSON value +const jsonTypeof = sql` +-- @db: db_mysql +-- @name: json typeof +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_TYPE(JSON_EXTRACT(data, '$.username')) AS username_type, + JSON_TYPE(JSON_EXTRACT(data, '$.age')) AS age_type, + JSON_TYPE(JSON_EXTRACT(data, '$.active')) AS active_type, + JSON_TYPE(JSON_EXTRACT(data, '$.items')) AS items_type, + JSON_TYPE(JSON_EXTRACT(data, '$.tags')) AS tags_type +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'shopping_cart', 'tags') +` + +// Test JSON_CONTAINS - check if JSON contains value +const jsonContains = sql` +-- @db: db_mysql +-- @name: json contains +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_CONTAINS(data, JSON_QUOTE('john_doe'), '$.username') AS has_specific_username, + JSON_CONTAINS(data, 'true', '$.active') AS is_active +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_CONTAINS_PATH - check if path exists +const jsonContainsPath = sql` +-- @db: db_mysql +-- @name: json contains path +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_CONTAINS_PATH(data, 'one', '$.username') AS has_username, + JSON_CONTAINS_PATH(data, 'one', '$.address') AS has_address, + JSON_CONTAINS_PATH(data, 'one', '$.nonexistent') AS has_nonexistent, + JSON_CONTAINS_PATH(data, 'all', '$.username', '$.email') AS has_both +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +` + +// Test JSON_OBJECT - build JSON objects +const jsonObjectBuild = sql` +-- @db: db_mysql +-- @name: json object build +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_OBJECT( + 'id', json_test_data.id, + 'name', json_test_data.name, + 'username', JSON_EXTRACT(data, '$.username'), + 'email', JSON_EXTRACT(data, '$.email') + ) AS user_summary +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_SET - update value in JSON +const jsonSet = sql` +-- @db: db_mysql +-- @name: json set +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_SET(data, '$.age', 31) AS updated_age, + JSON_SET(data, '$.address.city', 'New York') AS updated_city +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'user_with_address') +LIMIT 2 +` + +// Test JSON_INSERT - insert value into JSON +const jsonInsert = sql` +-- @db: db_mysql +-- @name: json insert +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_INSERT(data, '$.phone', '555-1234') AS with_phone +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_REPLACE - replace existing value +const jsonReplace = sql` +-- @db: db_mysql +-- @name: json replace +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_REPLACE(data, '$.username', 'new_username') AS with_new_username +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_REMOVE - remove keys from JSON +const jsonRemove = sql` +-- @db: db_mysql +-- @name: json remove +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_REMOVE(data, '$.age') AS without_age +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_MERGE_PATCH - merge JSON objects +const jsonMergePatch = sql` +-- @db: db_mysql +-- @name: json merge patch +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_MERGE_PATCH(data, JSON_OBJECT('verified', true, 'lastLogin', '2024-01-15')) AS merged_data +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_MERGE_PRESERVE - merge preserving all values +const jsonMergePreserve = sql` +-- @db: db_mysql +-- @name: json merge preserve +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + data AS original_data, + JSON_MERGE_PRESERVE(data, JSON_OBJECT('newField', 'newValue')) AS merged_data +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_SEARCH - find values in JSON +const jsonSearch = sql` +-- @db: db_mysql +-- @name: json search +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_SEARCH(data, 'one', 'john_doe') AS username_path, + JSON_SEARCH(data, 'one', 'john@example.com') AS email_path +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +` + +// Test JSON_DEPTH - get depth of JSON +const jsonDepth = sql` +-- @db: db_mysql +-- @db: db_mysql +-- @name: json depth +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_DEPTH(data) AS data_depth +FROM json_test_data +WHERE json_test_data.name IN ('user_profile', 'nested_config') +` + +// Test JSON_VALID - validate JSON +const jsonValid = sql` +-- @db: db_mysql +-- @db: db_mysql +-- @name: json valid +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_VALID(data) AS is_valid_json +FROM json_test_data +LIMIT 3 +` diff --git a/tests/demo_json/mysql/json_operations.snapshot.ts b/tests/demo_json/mysql/json_operations.snapshot.ts new file mode 100644 index 00000000..c78e66b3 --- /dev/null +++ b/tests/demo_json/mysql/json_operations.snapshot.ts @@ -0,0 +1,37 @@ +export type JsonObjectBasicParams = []; + +export interface IJsonObjectBasicResult { + id: number; + itemJson: any; +} + +export interface IJsonObjectBasicQuery { + params: JsonObjectBasicParams; + result: IJsonObjectBasicResult; +} + +export type JsonArrayAggregationParams = []; + +export interface IJsonArrayAggregationResult { + items: any; + rarity: string | null; +} + +export interface IJsonArrayAggregationQuery { + params: JsonArrayAggregationParams; + result: IJsonArrayAggregationResult; +} + +export type JsonOperatorsSelectParams = []; + +export interface IJsonOperatorsSelectResult { + extractedName: any; + id: number; + name: string; +} + +export interface IJsonOperatorsSelectQuery { + params: JsonOperatorsSelectParams; + result: IJsonOperatorsSelectResult; +} + From 968e80c3875b8043a1de2f3c4da3873302a3894f Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 15:49:56 +1100 Subject: [PATCH 15/19] fmt --- tests/demo_happy_path.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/demo_happy_path.rs b/tests/demo_happy_path.rs index 033f22a7..9ce9c394 100644 --- a/tests/demo_happy_path.rs +++ b/tests/demo_happy_path.rs @@ -94,7 +94,6 @@ mod demo_happy_path_tests { run_demo_test(&demo_path) } - #[test] fn all_demo_json_postgres() -> Result<(), Box> { // PostgreSQL JSON tests - compatible with all PostgreSQL versions that support JSON @@ -115,7 +114,6 @@ mod demo_happy_path_tests { run_demo_test(&demo_path) } - #[test] fn test_js_files() -> Result<(), Box> { // SETUP From 9b472f90126457ee304f7ca4db43920a1b06d127 Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 16:09:26 +1100 Subject: [PATCH 16/19] fix --- src/ts_generator/types/ts_query.rs | 1 + .../mysql/json_access_operators.queries.ts | 4 --- .../mysql/json_access_operators.snapshot.ts | 5 ---- .../mysql/json_array_functions.snapshot.ts | 1 - .../mysql/json_comprehensive.snapshot.ts | 1 - .../mysql/json_object_functions.snapshot.ts | 1 - .../mysql/json_operations.snapshot.ts | 25 ------------------- 7 files changed, 1 insertion(+), 37 deletions(-) diff --git a/src/ts_generator/types/ts_query.rs b/src/ts_generator/types/ts_query.rs index dc5159e4..3365a6ce 100644 --- a/src/ts_generator/types/ts_query.rs +++ b/src/ts_generator/types/ts_query.rs @@ -131,6 +131,7 @@ impl TsFieldType { "binary" | "bit" | "blob" | "char" | "text" | "varbinary" | "varchar" => Self::String, "tinyint" => Self::Boolean, "date" | "datetime" | "timestamp" => Self::Date, + "json" => Self::Object, "enum" => { if let Some(enum_values) = enum_values { return Self::Enum(enum_values); diff --git a/tests/demo_json/mysql/json_access_operators.queries.ts b/tests/demo_json/mysql/json_access_operators.queries.ts index 81a2595e..0577bd18 100644 --- a/tests/demo_json/mysql/json_access_operators.queries.ts +++ b/tests/demo_json/mysql/json_access_operators.queries.ts @@ -33,12 +33,9 @@ export type JsonNestedAccessParams = []; export interface IJsonNestedAccessResult { addressJson: string; - city: any; cityJson: string; id: number; name: string; - street: any; - zipCode: any; } export interface IJsonNestedAccessQuery { @@ -50,7 +47,6 @@ export type JsonArrayAccessParams = []; export interface IJsonArrayAccessResult { firstItemJson: string; - firstItemName: any; firstItemPrice: number; id: number; itemsJson: string; diff --git a/tests/demo_json/mysql/json_access_operators.snapshot.ts b/tests/demo_json/mysql/json_access_operators.snapshot.ts index c93b1dc7..0577bd18 100644 --- a/tests/demo_json/mysql/json_access_operators.snapshot.ts +++ b/tests/demo_json/mysql/json_access_operators.snapshot.ts @@ -33,12 +33,9 @@ export type JsonNestedAccessParams = []; export interface IJsonNestedAccessResult { addressJson: string; - city: any; cityJson: string; id: number; name: string; - street: any; - zipCode: any; } export interface IJsonNestedAccessQuery { @@ -50,7 +47,6 @@ export type JsonArrayAccessParams = []; export interface IJsonArrayAccessResult { firstItemJson: string; - firstItemName: any; firstItemPrice: number; id: number; itemsJson: string; @@ -126,4 +122,3 @@ export interface IJsonNullHandlingQuery { params: JsonNullHandlingParams; result: IJsonNullHandlingResult; } - diff --git a/tests/demo_json/mysql/json_array_functions.snapshot.ts b/tests/demo_json/mysql/json_array_functions.snapshot.ts index 1b069362..6b057d4f 100644 --- a/tests/demo_json/mysql/json_array_functions.snapshot.ts +++ b/tests/demo_json/mysql/json_array_functions.snapshot.ts @@ -128,4 +128,3 @@ export interface IJsonArrayInsertQuery { params: JsonArrayInsertParams; result: IJsonArrayInsertResult; } - diff --git a/tests/demo_json/mysql/json_comprehensive.snapshot.ts b/tests/demo_json/mysql/json_comprehensive.snapshot.ts index af43c197..1067842b 100644 --- a/tests/demo_json/mysql/json_comprehensive.snapshot.ts +++ b/tests/demo_json/mysql/json_comprehensive.snapshot.ts @@ -173,4 +173,3 @@ export interface IJsonSearchQuery { params: JsonSearchParams; result: IJsonSearchResult; } - diff --git a/tests/demo_json/mysql/json_object_functions.snapshot.ts b/tests/demo_json/mysql/json_object_functions.snapshot.ts index 35cb4bdf..37a6c0f4 100644 --- a/tests/demo_json/mysql/json_object_functions.snapshot.ts +++ b/tests/demo_json/mysql/json_object_functions.snapshot.ts @@ -208,4 +208,3 @@ export interface IJsonValidQuery { params: JsonValidParams; result: IJsonValidResult; } - diff --git a/tests/demo_json/mysql/json_operations.snapshot.ts b/tests/demo_json/mysql/json_operations.snapshot.ts index c78e66b3..2392c7e4 100644 --- a/tests/demo_json/mysql/json_operations.snapshot.ts +++ b/tests/demo_json/mysql/json_operations.snapshot.ts @@ -1,27 +1,3 @@ -export type JsonObjectBasicParams = []; - -export interface IJsonObjectBasicResult { - id: number; - itemJson: any; -} - -export interface IJsonObjectBasicQuery { - params: JsonObjectBasicParams; - result: IJsonObjectBasicResult; -} - -export type JsonArrayAggregationParams = []; - -export interface IJsonArrayAggregationResult { - items: any; - rarity: string | null; -} - -export interface IJsonArrayAggregationQuery { - params: JsonArrayAggregationParams; - result: IJsonArrayAggregationResult; -} - export type JsonOperatorsSelectParams = []; export interface IJsonOperatorsSelectResult { @@ -34,4 +10,3 @@ export interface IJsonOperatorsSelectQuery { params: JsonOperatorsSelectParams; result: IJsonOperatorsSelectResult; } - From 1bb666171701f623b98b3a886fbcf960a87621aa Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 16:58:10 +1100 Subject: [PATCH 17/19] fix --- .github/workflows/rust.yaml | 3 ++ .../function_handlers/json_functions.rs | 7 ++-- .../sql_parser/expressions/functions.rs | 9 ++++- src/ts_generator/types/ts_query.rs | 38 ++++++++++++++++++- .../mysql/json_comprehensive.queries.ts | 2 +- .../mysql/json_comprehensive.snapshot.ts | 3 +- .../mysql/json_object_functions.queries.ts | 2 +- .../mysql/json_object_functions.snapshot.ts | 3 +- .../demo_json/mysql/json_object_functions.ts | 2 - 9 files changed, 58 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml index a815e0e4..2db4185a 100644 --- a/.github/workflows/rust.yaml +++ b/.github/workflows/rust.yaml @@ -66,6 +66,9 @@ jobs: - name: Run tests run: cargo test -- --test-threads=1 + env: + MYSQL_VERSION: ${{ matrix.db.mysql }} + PG_VERSION: ${{ matrix.db.postgres }} lint: runs-on: ubuntu-latest diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs index b55b6c3f..3e646c16 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs @@ -97,7 +97,7 @@ pub async fn process_json_build_object_args( table_with_joins: &Option>, db_conn: &DBConn, ) -> Option> { - if !args.len().is_multiple_of(2) { + if args.len() % 2 != 0 { // Invalid number of arguments return None; } @@ -133,8 +133,9 @@ pub async fn handle_json_build_function( ) -> Result<(), TsGeneratorError> { let expr_log = ctx.expr_for_logging.unwrap_or(""); - // Handle jsonb_build_object / json_build_object - if function_name.to_uppercase() == "JSONB_BUILD_OBJECT" || function_name.to_uppercase() == "JSON_BUILD_OBJECT" { + // Handle jsonb_build_object / json_build_object / json_object (MySQL) + let func_upper = function_name.to_uppercase(); + if func_upper == "JSONB_BUILD_OBJECT" || func_upper == "JSON_BUILD_OBJECT" || func_upper == "JSON_OBJECT" { if let Some(object_fields) = process_json_build_object_args(args, ctx.single_table_name, ctx.table_with_joins, ctx.db_conn).await { diff --git a/src/ts_generator/sql_parser/expressions/functions.rs b/src/ts_generator/sql_parser/expressions/functions.rs index 60d9bb6c..8d086b90 100644 --- a/src/ts_generator/sql_parser/expressions/functions.rs +++ b/src/ts_generator/sql_parser/expressions/functions.rs @@ -177,10 +177,17 @@ pub static JSON_BUILD_FUNCTIONS: &[&str] = &[ "JSON_BUILD_OBJECT", "JSONB_BUILD_ARRAY", "JSON_BUILD_ARRAY", + "JSON_OBJECT", // MySQL JSON_OBJECT function ]; // JSON/JSONB aggregation functions -pub static JSON_AGG_FUNCTIONS: &[&str] = &["JSONB_AGG", "JSON_AGG", "JSON_OBJECT_AGG", "JSONB_OBJECT_AGG"]; +pub static JSON_AGG_FUNCTIONS: &[&str] = &[ + "JSONB_AGG", + "JSON_AGG", + "JSON_OBJECT_AGG", + "JSONB_OBJECT_AGG", + "JSON_ARRAYAGG", // MySQL JSON_ARRAYAGG function +]; pub fn is_json_build_function(func_name: &str) -> bool { JSON_BUILD_FUNCTIONS.contains(&func_name.to_uppercase().as_str()) diff --git a/src/ts_generator/types/ts_query.rs b/src/ts_generator/types/ts_query.rs index 3365a6ce..c202c1c8 100644 --- a/src/ts_generator/types/ts_query.rs +++ b/src/ts_generator/types/ts_query.rs @@ -10,6 +10,41 @@ use crate::ts_generator::errors::TsGeneratorError; type Array2DContent = Vec>; +/// Check if a string is a valid TypeScript identifier +fn is_valid_ts_identifier(name: &str) -> bool { + // TypeScript identifier regex: must start with letter/underscore/dollar, followed by letters/digits/underscore/dollar + let identifier_regex = Regex::new(r"^[a-zA-Z_$][a-zA-Z0-9_$]*$").unwrap(); + + if !identifier_regex.is_match(name) { + return false; + } + + // Check against TypeScript reserved keywords + let reserved_keywords = [ + "break", "case", "catch", "class", "const", "continue", "debugger", "default", + "delete", "do", "else", "enum", "export", "extends", "false", "finally", + "for", "function", "if", "import", "in", "instanceof", "new", "null", + "return", "super", "switch", "this", "throw", "true", "try", "typeof", + "var", "void", "while", "with", "as", "implements", "interface", "let", + "package", "private", "protected", "public", "static", "yield", "any", + "boolean", "constructor", "declare", "get", "module", "require", "number", + "set", "string", "symbol", "type", "from", "of", "namespace", "async", + "await", "abstract", "readonly", "never", "unknown", "bigint" + ]; + + !reserved_keywords.contains(&name) +} + +/// Format a field name for TypeScript object literal, quoting if necessary +fn format_ts_field_name(name: &str) -> String { + if is_valid_ts_identifier(name) { + name.to_string() + } else { + // Quote the field name and escape quotes inside + format!("\"{}\"", name.replace('\"', "\\\"")) + } +} + #[derive(Debug, Clone, PartialEq)] pub enum TsFieldType { String, @@ -45,7 +80,8 @@ impl fmt::Display for TsFieldType { } else { field_type.to_string() }; - format!("{}: {}", field_name, type_str) + let formatted_name = format_ts_field_name(field_name); + format!("{}: {}", formatted_name, type_str) }) .collect(); write!(f, "{{ {} }}", field_strings.join("; ")) diff --git a/tests/demo_json/mysql/json_comprehensive.queries.ts b/tests/demo_json/mysql/json_comprehensive.queries.ts index 1067842b..870eb844 100644 --- a/tests/demo_json/mysql/json_comprehensive.queries.ts +++ b/tests/demo_json/mysql/json_comprehensive.queries.ts @@ -113,7 +113,7 @@ export type JsonObjectBuildParams = []; export interface IJsonObjectBuildResult { id: number; name: string; - userSummary: any; + userSummary: { id: number; name: string; username: any; email: any }; } export interface IJsonObjectBuildQuery { diff --git a/tests/demo_json/mysql/json_comprehensive.snapshot.ts b/tests/demo_json/mysql/json_comprehensive.snapshot.ts index 1067842b..f3e50ea0 100644 --- a/tests/demo_json/mysql/json_comprehensive.snapshot.ts +++ b/tests/demo_json/mysql/json_comprehensive.snapshot.ts @@ -113,7 +113,7 @@ export type JsonObjectBuildParams = []; export interface IJsonObjectBuildResult { id: number; name: string; - userSummary: any; + userSummary: { id: number; name: string; username: any; email: any }; } export interface IJsonObjectBuildQuery { @@ -173,3 +173,4 @@ export interface IJsonSearchQuery { params: JsonSearchParams; result: IJsonSearchResult; } + diff --git a/tests/demo_json/mysql/json_object_functions.queries.ts b/tests/demo_json/mysql/json_object_functions.queries.ts index 37a6c0f4..8d6588e4 100644 --- a/tests/demo_json/mysql/json_object_functions.queries.ts +++ b/tests/demo_json/mysql/json_object_functions.queries.ts @@ -76,7 +76,7 @@ export type JsonObjectBuildParams = []; export interface IJsonObjectBuildResult { id: number; name: string; - userSummary: any; + userSummary: { id: number; name: string; username: any; email: any }; } export interface IJsonObjectBuildQuery { diff --git a/tests/demo_json/mysql/json_object_functions.snapshot.ts b/tests/demo_json/mysql/json_object_functions.snapshot.ts index 37a6c0f4..9537f5bb 100644 --- a/tests/demo_json/mysql/json_object_functions.snapshot.ts +++ b/tests/demo_json/mysql/json_object_functions.snapshot.ts @@ -76,7 +76,7 @@ export type JsonObjectBuildParams = []; export interface IJsonObjectBuildResult { id: number; name: string; - userSummary: any; + userSummary: { id: number; name: string; username: any; email: any }; } export interface IJsonObjectBuildQuery { @@ -208,3 +208,4 @@ export interface IJsonValidQuery { params: JsonValidParams; result: IJsonValidResult; } + diff --git a/tests/demo_json/mysql/json_object_functions.ts b/tests/demo_json/mysql/json_object_functions.ts index e1c9b920..20ba1f32 100644 --- a/tests/demo_json/mysql/json_object_functions.ts +++ b/tests/demo_json/mysql/json_object_functions.ts @@ -182,7 +182,6 @@ WHERE json_test_data.name = 'user_profile' // Test JSON_DEPTH - get depth of JSON const jsonDepth = sql` -- @db: db_mysql --- @db: db_mysql -- @name: json depth SELECT json_test_data.id AS id, @@ -195,7 +194,6 @@ WHERE json_test_data.name IN ('user_profile', 'nested_config') // Test JSON_VALID - validate JSON const jsonValid = sql` -- @db: db_mysql --- @db: db_mysql -- @name: json valid SELECT json_test_data.id AS id, From 455f25213218acac21cea4f75e461151cf2946af Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 16:58:59 +1100 Subject: [PATCH 18/19] fmt --- src/ts_generator/types/ts_query.rs | 77 ++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/src/ts_generator/types/ts_query.rs b/src/ts_generator/types/ts_query.rs index c202c1c8..a6e72c9e 100644 --- a/src/ts_generator/types/ts_query.rs +++ b/src/ts_generator/types/ts_query.rs @@ -21,15 +21,74 @@ fn is_valid_ts_identifier(name: &str) -> bool { // Check against TypeScript reserved keywords let reserved_keywords = [ - "break", "case", "catch", "class", "const", "continue", "debugger", "default", - "delete", "do", "else", "enum", "export", "extends", "false", "finally", - "for", "function", "if", "import", "in", "instanceof", "new", "null", - "return", "super", "switch", "this", "throw", "true", "try", "typeof", - "var", "void", "while", "with", "as", "implements", "interface", "let", - "package", "private", "protected", "public", "static", "yield", "any", - "boolean", "constructor", "declare", "get", "module", "require", "number", - "set", "string", "symbol", "type", "from", "of", "namespace", "async", - "await", "abstract", "readonly", "never", "unknown", "bigint" + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "as", + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", + "static", + "yield", + "any", + "boolean", + "constructor", + "declare", + "get", + "module", + "require", + "number", + "set", + "string", + "symbol", + "type", + "from", + "of", + "namespace", + "async", + "await", + "abstract", + "readonly", + "never", + "unknown", + "bigint", ]; !reserved_keywords.contains(&name) From 3cccb756d92a193fc62ed5f511b26f0a0bddc9ba Mon Sep 17 00:00:00 2001 From: JasonShin Date: Tue, 10 Mar 2026 17:10:22 +1100 Subject: [PATCH 19/19] fix --- .../function_handlers/json_functions.rs | 2 +- .../mysql/json_reserved_keywords.queries.ts | 37 +++++++++ .../mysql/json_reserved_keywords.snapshot.ts | 38 ++++++++++ .../demo_json/mysql/json_reserved_keywords.ts | 63 ++++++++++++++++ .../json_reserved_keywords.queries.ts | 48 ++++++++++++ .../json_reserved_keywords.snapshot.ts | 49 ++++++++++++ .../postgres/json_reserved_keywords.ts | 75 +++++++++++++++++++ 7 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 tests/demo_json/mysql/json_reserved_keywords.queries.ts create mode 100644 tests/demo_json/mysql/json_reserved_keywords.snapshot.ts create mode 100644 tests/demo_json/mysql/json_reserved_keywords.ts create mode 100644 tests/demo_json/postgres/json_reserved_keywords.queries.ts create mode 100644 tests/demo_json/postgres/json_reserved_keywords.snapshot.ts create mode 100644 tests/demo_json/postgres/json_reserved_keywords.ts diff --git a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs index 3e646c16..49ffb205 100644 --- a/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs +++ b/src/ts_generator/sql_parser/expressions/function_handlers/json_functions.rs @@ -97,7 +97,7 @@ pub async fn process_json_build_object_args( table_with_joins: &Option>, db_conn: &DBConn, ) -> Option> { - if args.len() % 2 != 0 { + if !args.len().is_multiple_of(2) { // Invalid number of arguments return None; } diff --git a/tests/demo_json/mysql/json_reserved_keywords.queries.ts b/tests/demo_json/mysql/json_reserved_keywords.queries.ts new file mode 100644 index 00000000..ae94faa2 --- /dev/null +++ b/tests/demo_json/mysql/json_reserved_keywords.queries.ts @@ -0,0 +1,37 @@ +export type JsonReservedKeywordsParams = []; + +export interface IJsonReservedKeywordsResult { + id: number; + name: string; + reservedKeywordsObject: { "class": string; "interface": string; "type": string; "const": string; "let": string; "function": string; "return": boolean; "import": string; "export": string; "async": string }; +} + +export interface IJsonReservedKeywordsQuery { + params: JsonReservedKeywordsParams; + result: IJsonReservedKeywordsResult; +} + +export type JsonInvalidIdentifiersParams = []; + +export interface IJsonInvalidIdentifiersResult { + id: number; + invalidIdentifiersObject: { "field-name": string; "field name": string; "123field": string; "user@email": string; "field.nested": string }; + name: string; +} + +export interface IJsonInvalidIdentifiersQuery { + params: JsonInvalidIdentifiersParams; + result: IJsonInvalidIdentifiersResult; +} + +export type JsonMixedIdentifiersParams = []; + +export interface IJsonMixedIdentifiersResult { + id: number; + mixedIdentifiersObject: { validName: string; "invalid-name": number; _underscore: string; $dollar: string; "class": string; "123start": string }; +} + +export interface IJsonMixedIdentifiersQuery { + params: JsonMixedIdentifiersParams; + result: IJsonMixedIdentifiersResult; +} diff --git a/tests/demo_json/mysql/json_reserved_keywords.snapshot.ts b/tests/demo_json/mysql/json_reserved_keywords.snapshot.ts new file mode 100644 index 00000000..ee739293 --- /dev/null +++ b/tests/demo_json/mysql/json_reserved_keywords.snapshot.ts @@ -0,0 +1,38 @@ +export type JsonReservedKeywordsParams = []; + +export interface IJsonReservedKeywordsResult { + id: number; + name: string; + reservedKeywordsObject: { "class": string; "interface": string; "type": string; "const": string; "let": string; "function": string; "return": boolean; "import": string; "export": string; "async": string }; +} + +export interface IJsonReservedKeywordsQuery { + params: JsonReservedKeywordsParams; + result: IJsonReservedKeywordsResult; +} + +export type JsonInvalidIdentifiersParams = []; + +export interface IJsonInvalidIdentifiersResult { + id: number; + invalidIdentifiersObject: { "field-name": string; "field name": string; "123field": string; "user@email": string; "field.nested": string }; + name: string; +} + +export interface IJsonInvalidIdentifiersQuery { + params: JsonInvalidIdentifiersParams; + result: IJsonInvalidIdentifiersResult; +} + +export type JsonMixedIdentifiersParams = []; + +export interface IJsonMixedIdentifiersResult { + id: number; + mixedIdentifiersObject: { validName: string; "invalid-name": number; _underscore: string; $dollar: string; "class": string; "123start": string }; +} + +export interface IJsonMixedIdentifiersQuery { + params: JsonMixedIdentifiersParams; + result: IJsonMixedIdentifiersResult; +} + diff --git a/tests/demo_json/mysql/json_reserved_keywords.ts b/tests/demo_json/mysql/json_reserved_keywords.ts new file mode 100644 index 00000000..ff97c1dd --- /dev/null +++ b/tests/demo_json/mysql/json_reserved_keywords.ts @@ -0,0 +1,63 @@ +import { sql } from 'sqlx-ts' + +// Test JSON_OBJECT with TypeScript reserved keywords +const jsonReservedKeywords = sql` +-- @db: db_mysql +-- @name: json reserved keywords +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_OBJECT( + 'class', 'User', + 'interface', 'IUser', + 'type', 'object', + 'const', 'constant', + 'let', 'variable', + 'function', 'method', + 'return', true, + 'import', 'module', + 'export', 'default', + 'async', 'promise' + ) AS reserved_keywords_object +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +LIMIT 1 +` + +// Test JSON_OBJECT with invalid TypeScript identifiers +const jsonInvalidIdentifiers = sql` +-- @db: db_mysql +-- @name: json invalid identifiers +SELECT + json_test_data.id AS id, + json_test_data.name AS name, + JSON_OBJECT( + 'field-name', 'hyphenated', + 'field name', 'with space', + '123field', 'starts with number', + 'user@email', 'special chars', + 'field.nested', 'dotted name' + ) AS invalid_identifiers_object +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +LIMIT 1 +` + +// Test mixed valid and invalid identifiers +const jsonMixedIdentifiers = sql` +-- @db: db_mysql +-- @name: json mixed identifiers +SELECT + json_test_data.id AS id, + JSON_OBJECT( + 'validName', json_test_data.name, + 'invalid-name', json_test_data.id, + '_underscore', 'valid', + '$dollar', 'valid', + 'class', 'reserved', + '123start', 'invalid' + ) AS mixed_identifiers_object +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +LIMIT 1 +` diff --git a/tests/demo_json/postgres/json_reserved_keywords.queries.ts b/tests/demo_json/postgres/json_reserved_keywords.queries.ts new file mode 100644 index 00000000..0751cc5c --- /dev/null +++ b/tests/demo_json/postgres/json_reserved_keywords.queries.ts @@ -0,0 +1,48 @@ +export type JsonbReservedKeywordsParams = []; + +export interface IJsonbReservedKeywordsResult { + jsonTestDataId: number; + jsonTestDataName: string; + reservedKeywordsObject: { "class": string; "interface": string; "type": string; "const": string; "let": string; "function": string; "return": boolean; "import": string; "export": string; "async": string }; +} + +export interface IJsonbReservedKeywordsQuery { + params: JsonbReservedKeywordsParams; + result: IJsonbReservedKeywordsResult; +} + +export type JsonbInvalidIdentifiersParams = []; + +export interface IJsonbInvalidIdentifiersResult { + invalidIdentifiersObject: { "field-name": string; "field name": string; "123field": string; "user@email": string; "field.nested": string }; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbInvalidIdentifiersQuery { + params: JsonbInvalidIdentifiersParams; + result: IJsonbInvalidIdentifiersResult; +} + +export type JsonbAggReservedKeywordsParams = []; + +export interface IJsonbAggReservedKeywordsResult { + aggregatedReservedKeywords: Array<{ "class": string; "interface": number; "default": boolean }>; +} + +export interface IJsonbAggReservedKeywordsQuery { + params: JsonbAggReservedKeywordsParams; + result: IJsonbAggReservedKeywordsResult; +} + +export type JsonbMixedIdentifiersParams = []; + +export interface IJsonbMixedIdentifiersResult { + jsonTestDataId: number; + mixedIdentifiersObject: { validName: string; "invalid-name": number; _underscore: string; $dollar: string; "class": string; "123start": string }; +} + +export interface IJsonbMixedIdentifiersQuery { + params: JsonbMixedIdentifiersParams; + result: IJsonbMixedIdentifiersResult; +} diff --git a/tests/demo_json/postgres/json_reserved_keywords.snapshot.ts b/tests/demo_json/postgres/json_reserved_keywords.snapshot.ts new file mode 100644 index 00000000..0ad81552 --- /dev/null +++ b/tests/demo_json/postgres/json_reserved_keywords.snapshot.ts @@ -0,0 +1,49 @@ +export type JsonbReservedKeywordsParams = []; + +export interface IJsonbReservedKeywordsResult { + jsonTestDataId: number; + jsonTestDataName: string; + reservedKeywordsObject: { "class": string; "interface": string; "type": string; "const": string; "let": string; "function": string; "return": boolean; "import": string; "export": string; "async": string }; +} + +export interface IJsonbReservedKeywordsQuery { + params: JsonbReservedKeywordsParams; + result: IJsonbReservedKeywordsResult; +} + +export type JsonbInvalidIdentifiersParams = []; + +export interface IJsonbInvalidIdentifiersResult { + invalidIdentifiersObject: { "field-name": string; "field name": string; "123field": string; "user@email": string; "field.nested": string }; + jsonTestDataId: number; + jsonTestDataName: string; +} + +export interface IJsonbInvalidIdentifiersQuery { + params: JsonbInvalidIdentifiersParams; + result: IJsonbInvalidIdentifiersResult; +} + +export type JsonbAggReservedKeywordsParams = []; + +export interface IJsonbAggReservedKeywordsResult { + aggregatedReservedKeywords: Array<{ "class": string; "interface": number; "default": boolean }>; +} + +export interface IJsonbAggReservedKeywordsQuery { + params: JsonbAggReservedKeywordsParams; + result: IJsonbAggReservedKeywordsResult; +} + +export type JsonbMixedIdentifiersParams = []; + +export interface IJsonbMixedIdentifiersResult { + jsonTestDataId: number; + mixedIdentifiersObject: { validName: string; "invalid-name": number; _underscore: string; $dollar: string; "class": string; "123start": string }; +} + +export interface IJsonbMixedIdentifiersQuery { + params: JsonbMixedIdentifiersParams; + result: IJsonbMixedIdentifiersResult; +} + diff --git a/tests/demo_json/postgres/json_reserved_keywords.ts b/tests/demo_json/postgres/json_reserved_keywords.ts new file mode 100644 index 00000000..4961e9de --- /dev/null +++ b/tests/demo_json/postgres/json_reserved_keywords.ts @@ -0,0 +1,75 @@ +import { sql } from 'sqlx-ts' + +// Test jsonb_build_object with TypeScript reserved keywords +const jsonbReservedKeywords = sql` +-- @name: jsonb reserved keywords +SELECT + json_test_data.id, + json_test_data.name, + jsonb_build_object( + 'class', 'User', + 'interface', 'IUser', + 'type', 'object', + 'const', 'constant', + 'let', 'variable', + 'function', 'method', + 'return', true, + 'import', 'module', + 'export', 'default', + 'async', 'promise' + ) AS reserved_keywords_object +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +LIMIT 1 +` + +// Test jsonb_build_object with invalid TypeScript identifiers +const jsonbInvalidIdentifiers = sql` +-- @name: jsonb invalid identifiers +SELECT + json_test_data.id, + json_test_data.name, + jsonb_build_object( + 'field-name', 'hyphenated', + 'field name', 'with space', + '123field', 'starts with number', + 'user@email', 'special chars', + 'field.nested', 'dotted name' + ) AS invalid_identifiers_object +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +LIMIT 1 +` + +// Test jsonb_agg with reserved keywords +const jsonbAggReservedKeywords = sql` +-- @name: jsonb agg reserved keywords +SELECT + jsonb_agg( + jsonb_build_object( + 'class', json_test_data.name, + 'interface', json_test_data.id, + 'default', true + ) + ) AS aggregated_reserved_keywords +FROM json_test_data +LIMIT 3 +` + +// Test mixed valid and invalid identifiers +const jsonbMixedIdentifiers = sql` +-- @name: jsonb mixed identifiers +SELECT + json_test_data.id, + jsonb_build_object( + 'validName', json_test_data.name, + 'invalid-name', json_test_data.id, + '_underscore', 'valid', + '$dollar', 'valid', + 'class', 'reserved', + '123start', 'invalid' + ) AS mixed_identifiers_object +FROM json_test_data +WHERE json_test_data.name = 'user_profile' +LIMIT 1 +`