├── .github ├── auto-author-assign-config.yml ├── release-drafter-config.yml └── workflows │ ├── auto-author-assign.yml │ ├── coverage.yml │ ├── docusaurus.yml │ ├── javascript.yml │ ├── prepare-release.yml │ ├── publish-docs.yml │ ├── publish-javascript.yml │ ├── publish-python.yml │ ├── publish-rust.yml │ ├── python.yml │ ├── release-drafter.yml │ └── rust.yml ├── .gitignore ├── AGENTS.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── cli ├── Cargo.toml ├── src │ ├── cli.rs │ ├── command.rs │ ├── helper.rs │ ├── lib.rs │ ├── main.rs │ └── print.rs └── tests │ └── dump.rs ├── core ├── Cargo.toml └── src │ ├── ast.rs │ ├── ast │ ├── ast_literal.rs │ ├── data_type.rs │ ├── ddl.rs │ ├── expr.rs │ ├── function.rs │ ├── operator.rs │ └── query.rs │ ├── ast_builder.rs │ ├── ast_builder │ ├── alter_table.rs │ ├── assignment.rs │ ├── build.rs │ ├── column_def.rs │ ├── column_list.rs │ ├── create_table.rs │ ├── data_type.rs │ ├── delete.rs │ ├── drop_table.rs │ ├── error.rs │ ├── execute.rs │ ├── expr.rs │ ├── expr │ │ ├── aggregate.rs │ │ ├── alias_as.rs │ │ ├── between.rs │ │ ├── binary_op.rs │ │ ├── case.rs │ │ ├── exists.rs │ │ ├── function.rs │ │ ├── in_list.rs │ │ ├── is_null.rs │ │ ├── like.rs │ │ ├── nested.rs │ │ ├── numeric.rs │ │ └── unary_op.rs │ ├── expr_list.rs │ ├── expr_with_alias.rs │ ├── index.rs │ ├── index_item.rs │ ├── index_item │ │ ├── cmp_expr.rs │ │ ├── non_clustered.rs │ │ └── primary_key.rs │ ├── insert.rs │ ├── order_by_expr.rs │ ├── order_by_expr_list.rs │ ├── query.rs │ ├── select.rs │ ├── select │ │ ├── filter.rs │ │ ├── group_by.rs │ │ ├── having.rs │ │ ├── join.rs │ │ ├── join │ │ │ ├── hash_join.rs │ │ │ ├── join_constraint.rs │ │ │ └── root.rs │ │ ├── limit.rs │ │ ├── offset.rs │ │ ├── offset_limit.rs │ │ ├── order_by.rs │ │ ├── project.rs │ │ ├── root.rs │ │ └── values.rs │ ├── select_item.rs │ ├── select_item_list.rs │ ├── show_columns.rs │ ├── table_factor.rs │ ├── table_name.rs │ ├── transaction.rs │ └── update.rs │ ├── data.rs │ ├── data │ ├── bigdecimal_ext.rs │ ├── function.rs │ ├── interval.rs │ ├── interval │ │ ├── error.rs │ │ ├── primitive.rs │ │ └── string.rs │ ├── key.rs │ ├── literal.rs │ ├── point.rs │ ├── row.rs │ ├── schema.rs │ ├── string_ext.rs │ ├── table.rs │ ├── value.rs │ └── value │ │ ├── binary_op.rs │ │ ├── binary_op │ │ ├── decimal.rs │ │ ├── f32.rs │ │ ├── f64.rs │ │ ├── integer.rs │ │ └── integer │ │ │ ├── i128.rs │ │ │ ├── i16.rs │ │ │ ├── i32.rs │ │ │ ├── i64.rs │ │ │ ├── i8.rs │ │ │ ├── macros.rs │ │ │ ├── u128.rs │ │ │ ├── u16.rs │ │ │ ├── u32.rs │ │ │ ├── u64.rs │ │ │ └── u8.rs │ │ ├── convert.rs │ │ ├── date.rs │ │ ├── error.rs │ │ ├── expr.rs │ │ ├── json.rs │ │ ├── literal.rs │ │ ├── selector.rs │ │ └── uuid.rs │ ├── executor.rs │ ├── executor │ ├── aggregate.rs │ ├── aggregate │ │ └── state.rs │ ├── alter.rs │ ├── alter │ │ ├── alter_table.rs │ │ ├── error.rs │ │ ├── function.rs │ │ ├── index.rs │ │ ├── table.rs │ │ └── validate.rs │ ├── context.rs │ ├── context │ │ ├── aggregate_context.rs │ │ └── row_context.rs │ ├── delete.rs │ ├── evaluate.rs │ ├── evaluate │ │ ├── error.rs │ │ ├── evaluated.rs │ │ ├── expr.rs │ │ └── function.rs │ ├── execute.rs │ ├── fetch.rs │ ├── filter.rs │ ├── insert.rs │ ├── join.rs │ ├── limit.rs │ ├── select.rs │ ├── select │ │ ├── error.rs │ │ └── project.rs │ ├── sort.rs │ ├── update.rs │ └── validate.rs │ ├── glue.rs │ ├── lib.rs │ ├── mock.rs │ ├── parse_sql.rs │ ├── plan.rs │ ├── plan │ ├── context.rs │ ├── error.rs │ ├── evaluable.rs │ ├── expr.rs │ ├── expr │ │ ├── aggregate.rs │ │ └── function.rs │ ├── index.rs │ ├── join.rs │ ├── planner.rs │ ├── primary_key.rs │ ├── schema.rs │ └── validate.rs │ ├── result.rs │ ├── store.rs │ ├── store │ ├── alter_table.rs │ ├── data_row.rs │ ├── function.rs │ ├── index.rs │ ├── metadata.rs │ └── transaction.rs │ ├── translate.rs │ └── translate │ ├── ast_literal.rs │ ├── data_type.rs │ ├── ddl.rs │ ├── error.rs │ ├── expr.rs │ ├── function.rs │ ├── operator.rs │ └── query.rs ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── blog │ ├── 2023-05-27-release-v0.14.md │ ├── 2023-05-29-breaking-the-boundary.md │ ├── 2023-05-30-revolutionizing-databases-by-unifying-query-interfaces.md │ ├── 2023-05-30-test-driven-documentation.md │ ├── 2023-11-18-release-v0.15.md │ └── assets │ │ ├── blog-test-driven-documentation-insert-errorcase.jpg │ │ └── blog-test-driven-documentation-insert.jpg ├── docs │ ├── ast-builder │ │ ├── _category_.json │ │ ├── expressions │ │ │ ├── _category_.json │ │ │ ├── conditional.md │ │ │ ├── nested.md │ │ │ ├── operator-based.md │ │ │ ├── pattern-matching.md │ │ │ └── value-checking.md │ │ ├── functions │ │ │ ├── _category_.json │ │ │ ├── date-&-time │ │ │ │ ├── _category_.json │ │ │ │ ├── conversion.md │ │ │ │ ├── current-date-and-time.md │ │ │ │ ├── date-and-time-extraction.md │ │ │ │ └── formatting.md │ │ │ ├── geometry │ │ │ │ ├── _category_.json │ │ │ │ ├── coordinate-extraction.md │ │ │ │ ├── distance-calculation.md │ │ │ │ └── point-creation.md │ │ │ ├── list-&-map │ │ │ │ ├── _category_.json │ │ │ │ ├── list-and-map-concatenation.md │ │ │ │ └── list-manipulation.md │ │ │ ├── math │ │ │ │ ├── _category_.json │ │ │ │ ├── basic-arithmetic.md │ │ │ │ ├── conversion.md │ │ │ │ ├── logarithmic-and-exponential.md │ │ │ │ ├── rounding.md │ │ │ │ ├── special-mathematical.md │ │ │ │ └── trigonometric.md │ │ │ ├── others │ │ │ │ ├── _category_.json │ │ │ │ ├── null-handling.md │ │ │ │ ├── type-conversion.md │ │ │ │ └── unique-identifier.md │ │ │ └── text │ │ │ │ ├── _category_.json │ │ │ │ ├── case-conversion.md │ │ │ │ ├── character-conversion.md │ │ │ │ ├── padding.md │ │ │ │ ├── position-and-indexing.md │ │ │ │ ├── text-manipulation.md │ │ │ │ └── trimming.md │ │ ├── intro.md │ │ └── statements │ │ │ ├── _category_.json │ │ │ ├── data-manipulation │ │ │ ├── _category_.json │ │ │ ├── deleting-data.md │ │ │ ├── inserting-data.md │ │ │ └── updating-data.md │ │ │ └── querying │ │ │ ├── _category_.json │ │ │ ├── creating-derived-subqueries.md │ │ │ ├── data-aggregation.md │ │ │ ├── data-injection.md │ │ │ ├── data-joining.md │ │ │ ├── data-selection-and-projection.md │ │ │ ├── data-sorting-and-limiting.md │ │ │ ├── fetching-data-from-storage.md │ │ │ └── using-preloaded-data.md │ ├── getting-started │ │ ├── _category_.json │ │ ├── cli.md │ │ ├── javascript-web.md │ │ ├── nodejs.md │ │ └── rust.md │ ├── index.md │ ├── sql-syntax │ │ ├── _category_.json │ │ ├── data-types │ │ │ ├── _category_.json │ │ │ ├── boolean.md │ │ │ ├── bytea.md │ │ │ ├── date.md │ │ │ ├── decimal.md │ │ │ ├── float.md │ │ │ ├── inet.md │ │ │ ├── integers.md │ │ │ ├── interval.md │ │ │ ├── list.md │ │ │ ├── map.md │ │ │ ├── text.md │ │ │ ├── time.md │ │ │ ├── timestamp.md │ │ │ └── uuid.md │ │ ├── functions │ │ │ ├── _category_.json │ │ │ ├── datetime │ │ │ │ ├── _category_.json │ │ │ │ ├── add-month.md │ │ │ │ ├── extract.md │ │ │ │ ├── format.md │ │ │ │ ├── last-day.md │ │ │ │ ├── now.md │ │ │ │ ├── to-date.md │ │ │ │ ├── to-time.md │ │ │ │ └── to-timestamp.md │ │ │ ├── geometry │ │ │ │ ├── _category_.json │ │ │ │ ├── calc-distance.md │ │ │ │ ├── get-x.md │ │ │ │ ├── get-y.md │ │ │ │ └── point.md │ │ │ ├── list-map │ │ │ │ ├── _category_.json │ │ │ │ ├── append.md │ │ │ │ ├── concat.md │ │ │ │ ├── dedup.md │ │ │ │ ├── entries.md │ │ │ │ ├── is-empty.md │ │ │ │ ├── keys.md │ │ │ │ ├── length.md │ │ │ │ ├── prepend.md │ │ │ │ ├── skip.md │ │ │ │ ├── slice.md │ │ │ │ ├── sort.md │ │ │ │ ├── splice.md │ │ │ │ ├── take.md │ │ │ │ └── values.md │ │ │ ├── math │ │ │ │ ├── _category_.json │ │ │ │ ├── abs.md │ │ │ │ ├── acos.md │ │ │ │ ├── asin.md │ │ │ │ ├── atan.md │ │ │ │ ├── ceil.md │ │ │ │ ├── cos.md │ │ │ │ ├── degrees.md │ │ │ │ ├── div.md │ │ │ │ ├── exp.md │ │ │ │ ├── floor.md │ │ │ │ ├── gcd.md │ │ │ │ ├── lcm.md │ │ │ │ ├── ln.md │ │ │ │ ├── log.md │ │ │ │ ├── log10.md │ │ │ │ ├── log2.md │ │ │ │ ├── mod.md │ │ │ │ ├── pi.md │ │ │ │ ├── power.md │ │ │ │ ├── radians.md │ │ │ │ ├── rand.md │ │ │ │ ├── round.md │ │ │ │ ├── sign.md │ │ │ │ ├── sin.md │ │ │ │ ├── sqrt.md │ │ │ │ └── tan.md │ │ │ ├── others │ │ │ │ ├── _category_.json │ │ │ │ ├── cast.md │ │ │ │ ├── coalesce.md │ │ │ │ ├── generate-uuid.md │ │ │ │ ├── greatest.md │ │ │ │ └── ifnull.md │ │ │ └── text │ │ │ │ ├── _category_.json │ │ │ │ ├── ascii.md │ │ │ │ ├── chr.md │ │ │ │ ├── concat-ws.md │ │ │ │ ├── concat.md │ │ │ │ ├── find-idx.md │ │ │ │ ├── initcap.md │ │ │ │ ├── left.md │ │ │ │ ├── lower.md │ │ │ │ ├── lpad.md │ │ │ │ ├── ltrim.md │ │ │ │ ├── md5.md │ │ │ │ ├── position.md │ │ │ │ ├── repeat.md │ │ │ │ ├── reverse.md │ │ │ │ ├── right.md │ │ │ │ ├── rpad.md │ │ │ │ ├── rtrim.md │ │ │ │ ├── substr.md │ │ │ │ ├── trim.md │ │ │ │ └── upper.md │ │ ├── intro.md │ │ └── statements │ │ │ ├── _category_.json │ │ │ ├── data-definition │ │ │ ├── _category_.json │ │ │ ├── alter-table.md │ │ │ ├── create-index.md │ │ │ ├── create-table.md │ │ │ ├── drop-index.md │ │ │ └── drop-table.md │ │ │ ├── data-manipulation │ │ │ ├── _category_.json │ │ │ ├── delete.md │ │ │ ├── insert.md │ │ │ └── update.md │ │ │ ├── metadata │ │ │ ├── _category_.json │ │ │ ├── data-dictionary.md │ │ │ └── show-tables.md │ │ │ ├── querying │ │ │ ├── _category_.json │ │ │ ├── aggregation.md │ │ │ ├── join.md │ │ │ ├── limit.md │ │ │ ├── schemaless.md │ │ │ └── where.md │ │ │ └── transaction.md │ └── storages │ │ ├── _category_.json │ │ ├── developing-custom-storages │ │ ├── _category_.json │ │ ├── intro.md │ │ ├── store-traits │ │ │ ├── _category_.json │ │ │ ├── alter-table.md │ │ │ ├── custom-function-mut.md │ │ │ ├── custom-function.md │ │ │ ├── index-mut.md │ │ │ ├── index-trait.md │ │ │ ├── metadata.md │ │ │ ├── store-mut.md │ │ │ ├── store.md │ │ │ └── transaction.md │ │ └── using-test-suite.md │ │ ├── intro.md │ │ └── supported-storages │ │ ├── _category_.json │ │ ├── composite-storage.md │ │ ├── csv-storage.md │ │ ├── file-storage.md │ │ ├── git-storage.md │ │ ├── idb-storage.md │ │ ├── json-storage.md │ │ ├── memory-storage.md │ │ ├── mongo-storage.md │ │ ├── parquet-storage.md │ │ ├── redb-storage.md │ │ ├── redis-storage.md │ │ ├── shared-memory-storage.md │ │ ├── sled-storage.md │ │ └── web-storage.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ └── css │ │ └── custom.css └── static │ ├── .nojekyll │ └── img │ ├── favicon.ico │ ├── gluesql.jpg │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ └── undraw_docusaurus_tree.svg ├── pkg ├── javascript │ ├── .gitignore │ ├── Cargo.toml │ ├── DEVELOPMENT.md │ ├── README.md │ ├── examples │ │ ├── nodejs │ │ │ └── main.js │ │ └── web │ │ │ ├── module │ │ │ ├── README.md │ │ │ └── index.html │ │ │ ├── rollup │ │ │ ├── index.html │ │ │ ├── main.js │ │ │ ├── package.json │ │ │ └── rollup.config.js │ │ │ └── webpack │ │ │ ├── index.html │ │ │ ├── main.js │ │ │ ├── package.json │ │ │ └── webpack.config.js │ ├── gluesql.js │ ├── gluesql.node.js │ ├── gluesql.rollup.js │ ├── package.json │ ├── src │ │ ├── lib.rs │ │ ├── payload.rs │ │ └── utils.rs │ ├── tests │ │ ├── composite_storage.rs │ │ ├── error.rs │ │ ├── join_multiple_storages.rs │ │ └── payload.rs │ └── webdriver.json ├── python │ ├── Cargo.toml │ ├── DEVELOPMENT.md │ ├── README.md │ ├── examples │ │ ├── json-storage │ │ │ ├── fixtures │ │ │ │ └── test.json │ │ │ └── main.py │ │ ├── memory-storage │ │ │ └── main.py │ │ ├── shared-memory-storage │ │ │ └── main.py │ │ └── sled-storage │ │ │ └── main.py │ ├── gluesql.pyi │ ├── pyproject.toml │ ├── pytest.ini │ ├── requirements.txt │ ├── src │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── payload.rs │ │ └── storages.rs │ └── tests │ │ └── payload.py └── rust │ ├── Cargo.toml │ ├── examples │ ├── api_usage.rs │ ├── hello_ast_builder.rs │ ├── hello_world.rs │ ├── memory_storage_usage.rs │ ├── sled_multi_threaded.rs │ └── using_config.rs │ ├── src │ ├── lib.rs │ └── main.rs │ └── tests │ └── glue.rs ├── rust-toolchain.toml ├── storages ├── composite-storage │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ ├── store.rs │ │ ├── store_mut.rs │ │ └── transaction.rs │ └── tests │ │ ├── basic.rs │ │ ├── composite_storage.rs │ │ ├── error.rs │ │ └── memory_and_sled.rs ├── csv-storage │ ├── Cargo.toml │ ├── src │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── store.rs │ │ └── store_mut.rs │ └── tests │ │ ├── basic.rs │ │ ├── csv_storage.rs │ │ ├── error.rs │ │ ├── samples │ │ ├── Book.csv │ │ ├── Book.sql │ │ ├── City.csv │ │ ├── City.sql │ │ ├── Employee.csv │ │ ├── Grocery.csv │ │ ├── Grocery.types.csv │ │ ├── Student.csv │ │ ├── Student.sql │ │ ├── Student.types.csv │ │ └── WrongSchemaName.sql │ │ ├── schema.rs │ │ ├── schemaless.rs │ │ ├── schemaless_without_types.rs │ │ └── types_only.rs ├── file-storage │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ ├── store.rs │ │ └── store_mut.rs │ └── tests │ │ └── file_storage.rs ├── git-storage │ ├── Cargo.toml │ ├── src │ │ ├── command_ext.rs │ │ ├── lib.rs │ │ ├── store.rs │ │ └── store_mut.rs │ └── tests │ │ ├── git_storage_csv.rs │ │ ├── git_storage_file.rs │ │ ├── git_storage_json.rs │ │ └── pull_and_push.rs ├── idb-storage │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── convert.rs │ │ ├── error.rs │ │ └── lib.rs │ └── tests │ │ ├── convert.rs │ │ └── idb_storage.rs ├── json-storage │ ├── Cargo.toml │ ├── src │ │ ├── alter_table.rs │ │ ├── error.rs │ │ ├── function.rs │ │ ├── index.rs │ │ ├── lib.rs │ │ ├── store.rs │ │ ├── store_mut.rs │ │ └── transaction.rs │ └── tests │ │ ├── error.rs │ │ ├── json_dml.rs │ │ ├── json_storage.rs │ │ ├── primary_key.rs │ │ ├── samples │ │ ├── ArrayOfJsonsSchema.json │ │ ├── ArrayOfJsonsSchema.sql │ │ ├── ArrayOfJsonsSchemaless.json │ │ ├── Duplicated.json │ │ ├── Duplicated.jsonl │ │ ├── JsonArrayTypeRequired.json │ │ ├── JsonObjectTypeRequired.json │ │ ├── Schema.jsonl │ │ ├── Schema.sql │ │ ├── Schemaless.jsonl │ │ ├── SingleJsonSchema.json │ │ ├── SingleJsonSchema.sql │ │ ├── SingleJsonSchemaless.json │ │ ├── WrongFormatJson.json │ │ ├── WrongFormatJsonl.jsonl │ │ ├── WrongSchema.jsonl │ │ ├── WrongSchema.sql │ │ ├── WrongTableName.jsonl │ │ └── WrongTableName.sql │ │ ├── schema.rs │ │ └── schemaless.rs ├── memory-storage │ ├── Cargo.toml │ ├── src │ │ ├── alter_table.rs │ │ ├── index.rs │ │ ├── lib.rs │ │ ├── metadata.rs │ │ └── transaction.rs │ └── tests │ │ └── memory_storage.rs ├── mongo-storage │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── description.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── row.rs │ │ ├── row │ │ │ ├── data_type.rs │ │ │ ├── key.rs │ │ │ └── value.rs │ │ ├── store.rs │ │ ├── store_mut.rs │ │ └── utils.rs │ └── tests │ │ ├── mongo_indexes.rs │ │ ├── mongo_schemaless.rs │ │ ├── mongo_storage.rs │ │ └── mongo_types.rs ├── parquet-storage │ ├── Cargo.toml │ ├── src │ │ ├── alter_table.rs │ │ ├── column_def.rs │ │ ├── error.rs │ │ ├── function.rs │ │ ├── index.rs │ │ ├── lib.rs │ │ ├── store.rs │ │ ├── store_mut.rs │ │ ├── transaction.rs │ │ └── value.rs │ └── tests │ │ ├── error.rs │ │ ├── parquet_storage.rs │ │ ├── samples │ │ ├── all_types_with_nulls.parquet │ │ ├── alltypes_dictionary.parquet │ │ ├── alltypes_plain.parquet │ │ ├── alltypes_plain_snappy.parquet │ │ ├── nested_lists_snappy.parquet │ │ └── nested_maps_snappy.parquet │ │ └── schema.rs ├── redb-storage │ ├── Cargo.toml │ ├── src │ │ ├── core.rs │ │ ├── error.rs │ │ └── lib.rs │ └── tests │ │ ├── begin_write_error.rs │ │ ├── nested_transaction.rs │ │ ├── redb_storage.rs │ │ ├── reserved_table_name.rs │ │ └── storage_interface_error.rs ├── redis-storage │ ├── Cargo.toml │ ├── src │ │ ├── alter_table.rs │ │ ├── index.rs │ │ ├── lib.rs │ │ ├── metadata.rs │ │ └── transaction.rs │ └── tests │ │ ├── redis-storage.toml │ │ ├── redis_errors.rs │ │ ├── redis_reconnect.rs │ │ ├── redis_store.rs │ │ └── redis_table.rs ├── shared-memory-storage │ ├── Cargo.toml │ ├── src │ │ ├── alter_table.rs │ │ ├── index.rs │ │ ├── lib.rs │ │ └── transaction.rs │ └── tests │ │ ├── concurrent_access.rs │ │ └── shared_memory_storage.rs ├── sled-storage │ ├── Cargo.toml │ ├── benches │ │ └── sled_benchmark.rs │ ├── src │ │ ├── alter_table.rs │ │ ├── error.rs │ │ ├── gc.rs │ │ ├── index.rs │ │ ├── index_mut.rs │ │ ├── index_sync.rs │ │ ├── key.rs │ │ ├── lib.rs │ │ ├── lock.rs │ │ ├── snapshot.rs │ │ ├── store.rs │ │ ├── store_mut.rs │ │ └── transaction.rs │ └── tests │ │ ├── export_and_import.rs │ │ ├── sled_storage.rs │ │ └── sled_transaction.rs └── web-storage │ ├── Cargo.toml │ ├── README.md │ ├── src │ └── lib.rs │ └── tests │ ├── local_storage.rs │ └── session_storage.rs ├── test-suite ├── Cargo.toml └── src │ ├── aggregate.rs │ ├── aggregate │ ├── avg.rs │ ├── count.rs │ ├── error.rs │ ├── group_by.rs │ ├── max.rs │ ├── min.rs │ ├── stdev.rs │ ├── sum.rs │ └── variance.rs │ ├── alter.rs │ ├── alter │ ├── alter_table.rs │ ├── create_table.rs │ ├── drop_indexed.rs │ └── drop_table.rs │ ├── arithmetic.rs │ ├── arithmetic │ ├── error.rs │ ├── on_where.rs │ └── project.rs │ ├── array.rs │ ├── ast_builder.rs │ ├── ast_builder │ ├── alias_as.rs │ ├── basic.rs │ ├── delete.rs │ ├── expr.rs │ ├── expr │ │ └── pattern_matching.rs │ ├── function.rs │ ├── function │ │ ├── datetime.rs │ │ ├── datetime │ │ │ ├── conversion.rs │ │ │ ├── current_date_and_time.rs │ │ │ └── formatting.rs │ │ ├── math.rs │ │ ├── math │ │ │ ├── basic_arithmetic.rs │ │ │ ├── conversion.rs │ │ │ └── rounding.rs │ │ ├── other.rs │ │ ├── other │ │ │ ├── coalesce.rs │ │ │ └── ifnull.rs │ │ ├── text.rs │ │ └── text │ │ │ ├── case_conversion.rs │ │ │ ├── character_conversion.rs │ │ │ ├── padding.rs │ │ │ ├── position_and_indexing.rs │ │ │ └── trimming.rs │ ├── index_by.rs │ ├── insert.rs │ ├── schemaless.rs │ ├── schemaless │ │ └── basic.rs │ ├── select.rs │ ├── statements.rs │ ├── statements │ │ ├── querying.rs │ │ └── querying │ │ │ ├── data_aggregation.rs │ │ │ └── data_selection_and_projection.rs │ ├── update.rs │ └── values.rs │ ├── basic.rs │ ├── bitwise_and.rs │ ├── bitwise_shift_left.rs │ ├── bitwise_shift_right.rs │ ├── case.rs │ ├── column_alias.rs │ ├── concat.rs │ ├── custom_function.rs │ ├── data_type.rs │ ├── data_type │ ├── bytea.rs │ ├── date.rs │ ├── decimal.rs │ ├── float32.rs │ ├── inet.rs │ ├── int128.rs │ ├── int16.rs │ ├── int32.rs │ ├── int64.rs │ ├── int8.rs │ ├── interval.rs │ ├── list.rs │ ├── map.rs │ ├── point.rs │ ├── sql_types.rs │ ├── time.rs │ ├── timestamp.rs │ ├── uint128.rs │ ├── uint16.rs │ ├── uint32.rs │ ├── uint64.rs │ ├── uint8.rs │ └── uuid.rs │ ├── default.rs │ ├── delete.rs │ ├── dictionary.rs │ ├── dictionary_index.rs │ ├── filter.rs │ ├── foreign_key.rs │ ├── function.rs │ ├── function │ ├── abs.rs │ ├── add_month.rs │ ├── append.rs │ ├── ascii.rs │ ├── cast.rs │ ├── ceil.rs │ ├── chr.rs │ ├── coalesce.rs │ ├── concat.rs │ ├── concat_ws.rs │ ├── dedup.rs │ ├── degrees.rs │ ├── div_mod.rs │ ├── entries.rs │ ├── exp_log.rs │ ├── extract.rs │ ├── find_idx.rs │ ├── floor.rs │ ├── format.rs │ ├── gcd_lcm.rs │ ├── generate_uuid.rs │ ├── geometry.rs │ ├── geometry │ │ ├── calc_distance.rs │ │ ├── get_x.rs │ │ └── get_y.rs │ ├── greatest.rs │ ├── ifnull.rs │ ├── initcap.rs │ ├── is_empty.rs │ ├── keys.rs │ ├── last_day.rs │ ├── left_right.rs │ ├── length.rs │ ├── lpad_rpad.rs │ ├── ltrim_rtrim.rs │ ├── math_function.rs │ ├── md5.rs │ ├── now.rs │ ├── pi.rs │ ├── position.rs │ ├── prepend.rs │ ├── radians.rs │ ├── rand.rs │ ├── repeat.rs │ ├── replace.rs │ ├── reverse.rs │ ├── round.rs │ ├── sign.rs │ ├── skip.rs │ ├── slice.rs │ ├── sort.rs │ ├── splice.rs │ ├── sqrt_power.rs │ ├── substr.rs │ ├── take.rs │ ├── to_date.rs │ ├── trim.rs │ ├── upper_lower.rs │ └── values.rs │ ├── index.rs │ ├── index │ ├── and.rs │ ├── basic.rs │ ├── expr.rs │ ├── nested.rs │ ├── null.rs │ ├── order_by.rs │ ├── showindexes.rs │ └── value.rs │ ├── inline_view.rs │ ├── insert.rs │ ├── join.rs │ ├── lib.rs │ ├── like_ilike.rs │ ├── limit.rs │ ├── metadata.rs │ ├── metadata │ ├── index.rs │ └── table.rs │ ├── migrate.rs │ ├── nested_select.rs │ ├── nullable.rs │ ├── order_by.rs │ ├── ordering.rs │ ├── primary_key.rs │ ├── project.rs │ ├── schemaless.rs │ ├── schemaless │ ├── basic.rs │ └── error.rs │ ├── series.rs │ ├── show_columns.rs │ ├── store.rs │ ├── store │ └── insert_schema.rs │ ├── synthesize.rs │ ├── tester.rs │ ├── tester │ └── macros.rs │ ├── transaction.rs │ ├── transaction │ ├── alter_table.rs │ ├── ast_builder.rs │ ├── basic.rs │ ├── dictionary.rs │ ├── index.rs │ └── table.rs │ ├── type_match.rs │ ├── unary_operator.rs │ ├── update.rs │ ├── validate.rs │ ├── validate │ ├── types.rs │ └── unique.rs │ └── values.rs └── utils ├── Cargo.toml └── src ├── hashmap.rs ├── indexmap.rs ├── lib.rs ├── or_stream.rs └── vector.rs /.github/auto-author-assign-config.yml: -------------------------------------------------------------------------------- 1 | addAssignees: author 2 | 3 | addReviewers: true 4 | reviewers: 5 | - panarch 6 | - ever0de 7 | - devgony 8 | - zmrdltl 9 | numberOfReviewers: 0 10 | 11 | runOnDraft: true 12 | -------------------------------------------------------------------------------- /.github/release-drafter-config.yml: -------------------------------------------------------------------------------- 1 | name-template: "v$RESOLVED_VERSION 🌈" 2 | tag-template: "v$RESOLVED_VERSION" 3 | categories: 4 | - title: "🚀 Features" 5 | labels: "enhancement" 6 | - title: "🐛 Bug Fixes" 7 | labels: "bug" 8 | change-template: "- $TITLE @$AUTHOR (#$NUMBER)" 9 | change-title-escapes: '\<*_&' 10 | version-resolver: 11 | major: 12 | labels: 13 | - "major" 14 | minor: 15 | labels: 16 | - "minor" 17 | patch: 18 | labels: 19 | - "patch" 20 | default: patch 21 | template: | 22 | ## Changes 23 | 24 | $CHANGES 25 | -------------------------------------------------------------------------------- /.github/workflows/auto-author-assign.yml: -------------------------------------------------------------------------------- 1 | name: auto-author-assign 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened] 6 | 7 | permissions: 8 | pull-requests: write 9 | 10 | jobs: 11 | assign-author: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: kentaro-m/auto-assign-action@v1.2.5 15 | with: 16 | configuration-path: '.github/auto-author-assign-config.yml' 17 | -------------------------------------------------------------------------------- /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | name: Python 2 | 3 | on: 4 | push: 5 | branches: [main, release-*] 6 | paths-ignore: 7 | - 'docs/**' 8 | pull_request: 9 | paths-ignore: 10 | - 'docs/**' 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | python_storage_tests: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Python 21 | uses: actions/setup-python@v5 22 | with: 23 | python-version: "3.11.4" 24 | 25 | - name: Set up pip and requirements 26 | run: | 27 | cd pkg/python 28 | python -m pip install --upgrade pip 29 | pip install -r requirements.txt 30 | 31 | - name: Set up Rust toolchain 32 | uses: actions-rs/toolchain@v1 33 | with: 34 | toolchain: stable 35 | override: true 36 | 37 | - name: Build and Test 38 | run: | 39 | cd pkg/python 40 | maturin build 41 | pip install . 42 | pytest 43 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v5 13 | with: 14 | config-name: release-drafter-config.yml 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # GlueSQL project files 2 | /data/ 3 | /pkg/rust/data/ 4 | /cli/tmp/ 5 | /storages/**/data/ 6 | /storages/**/tmp/ 7 | /reports/ 8 | /docs/gluesql.github.io 9 | 10 | # Vim 11 | *.swp 12 | 13 | # Visual Studio Code 14 | .vscode 15 | 16 | # Clion 17 | .idea 18 | 19 | # Mac-OS 20 | .DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Generated by Cargo 25 | # will have compiled files and executables 26 | /target/ 27 | /**/target/ 28 | 29 | # These are backup files generated by rustfmt 30 | **/*.rs.bk 31 | 32 | __pycache__ 33 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # AGENTS.md 2 | - After editing code, run `cargo clippy --all-targets -- -D warnings`. 3 | - Then run `cargo fmt --all`. 4 | - Commit only when both commands succeed. 5 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-cli" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | gluesql_sled_storage.workspace = true 14 | gluesql_memory_storage.workspace = true 15 | gluesql-json-storage.workspace = true 16 | gluesql-csv-storage.workspace = true 17 | gluesql-parquet-storage.workspace = true 18 | gluesql-file-storage.workspace = true 19 | gluesql-redb-storage.workspace = true 20 | 21 | clap = { version = "3.2.2", features = ["derive"] } 22 | rustyline = "9.1" 23 | rustyline-derive = "0.6" 24 | tabled = "0.8" 25 | thiserror = "1.0" 26 | edit = "0.1.4" 27 | futures = "0.3" 28 | anyhow = "1.0" 29 | strum_macros = "0.25" 30 | 31 | [dev-dependencies] 32 | tokio = { version = "1", features = ["rt", "macros"] } 33 | -------------------------------------------------------------------------------- /cli/src/helper.rs: -------------------------------------------------------------------------------- 1 | use { 2 | rustyline::{ 3 | Result, 4 | validate::{ValidationContext, ValidationResult, Validator}, 5 | }, 6 | rustyline_derive::{Completer, Helper, Highlighter, Hinter}, 7 | }; 8 | 9 | #[derive(Completer, Helper, Highlighter, Hinter)] 10 | pub struct CliHelper; 11 | 12 | impl Validator for CliHelper { 13 | fn validate(&self, ctx: &mut ValidationContext<'_>) -> Result<ValidationResult> { 14 | let input = ctx.input().trim(); 15 | 16 | if input.ends_with(';') || input.starts_with('.') { 17 | Ok(ValidationResult::Valid(None)) 18 | } else { 19 | Ok(ValidationResult::Incomplete) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | gluesql_cli::run().unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-core" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | utils.workspace = true 13 | 14 | regex = "1" 15 | async-trait = "0.1" 16 | async-recursion = "1" 17 | cfg-if = "1" 18 | futures-enum = "0.1.17" 19 | futures = "0.3" 20 | chrono = { version = "0.4.38", features = ["serde", "wasmbind"] } 21 | rust_decimal = { version = "1", features = ["serde-str"] } 22 | im-rc = "15" 23 | iter-enum = "1" 24 | itertools = "0.12" 25 | serde = { version = "1", features = ["derive"] } 26 | serde_json = "1" 27 | sqlparser = { version = "0.52", features = ["serde", "bigdecimal"] } 28 | thiserror = "1.0" 29 | strum_macros = "0.25" 30 | bigdecimal = { version = "0.4.1", features = ["serde", "string-only"] } 31 | hex = "0.4" 32 | rand = "0.8" 33 | ordered-float = { version = "4", features = ["serde"] } 34 | md-5 = "0.10.5" 35 | 36 | [target.'cfg(target_arch = "wasm32")'.dependencies.uuid] 37 | version = "1" 38 | features = ["v4", "js"] 39 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies.uuid] 40 | version = "1" 41 | features = ["v4"] 42 | 43 | [dev-dependencies] 44 | pretty_assertions = "1" 45 | -------------------------------------------------------------------------------- /core/src/ast/data_type.rs: -------------------------------------------------------------------------------- 1 | use { 2 | serde::{Deserialize, Serialize}, 3 | strum_macros::Display, 4 | }; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display)] 7 | #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] 8 | pub enum DataType { 9 | Boolean, 10 | Int8, 11 | Int16, 12 | Int32, 13 | Int, 14 | Int128, 15 | Uint8, 16 | Uint16, 17 | Uint32, 18 | Uint64, 19 | Uint128, 20 | Float32, 21 | Float, 22 | Text, 23 | Bytea, 24 | Inet, 25 | Date, 26 | Timestamp, 27 | Time, 28 | Interval, 29 | Uuid, 30 | Map, 31 | List, 32 | Decimal, 33 | Point, 34 | } 35 | -------------------------------------------------------------------------------- /core/src/ast_builder/build.rs: -------------------------------------------------------------------------------- 1 | use crate::{ast::Statement, result::Result}; 2 | 3 | pub trait Build { 4 | fn build(self) -> Result<Statement>; 5 | } 6 | -------------------------------------------------------------------------------- /core/src/ast_builder/column_def.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::ColumnDef, 3 | parse_sql::parse_column_def, 4 | result::{Error, Result}, 5 | translate::translate_column_def, 6 | }; 7 | 8 | #[derive(Clone, Debug)] 9 | pub enum ColumnDefNode { 10 | Text(String), 11 | } 12 | 13 | impl From<&str> for ColumnDefNode { 14 | fn from(column_def: &str) -> Self { 15 | ColumnDefNode::Text(column_def.to_owned()) 16 | } 17 | } 18 | 19 | impl TryFrom<ColumnDefNode> for ColumnDef { 20 | type Error = Error; 21 | 22 | fn try_from(column_def_node: ColumnDefNode) -> Result<ColumnDef> { 23 | match column_def_node { 24 | ColumnDefNode::Text(column_def) => parse_column_def(column_def) 25 | .and_then(|column_def| translate_column_def(&column_def)), 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/ast_builder/column_list.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | parse_sql::parse_identifiers, 3 | result::{Error, Result}, 4 | translate::translate_idents, 5 | }; 6 | 7 | #[derive(Clone, Debug)] 8 | pub enum ColumnList { 9 | Text(String), 10 | Columns(Vec<String>), 11 | } 12 | 13 | impl From<&str> for ColumnList { 14 | fn from(columns: &str) -> Self { 15 | ColumnList::Text(columns.to_owned()) 16 | } 17 | } 18 | 19 | impl From<Vec<&str>> for ColumnList { 20 | fn from(columns: Vec<&str>) -> Self { 21 | ColumnList::Columns(columns.into_iter().map(ToOwned::to_owned).collect()) 22 | } 23 | } 24 | 25 | impl TryFrom<ColumnList> for Vec<String> { 26 | type Error = Error; 27 | 28 | fn try_from(column_list: ColumnList) -> Result<Self> { 29 | match column_list { 30 | ColumnList::Text(columns) => { 31 | let idents = parse_identifiers(columns)?; 32 | Ok(translate_idents(idents.as_slice())) 33 | } 34 | ColumnList::Columns(columns) => Ok(columns), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/ast_builder/data_type.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::DataType, 3 | parse_sql::parse_data_type, 4 | result::{Error, Result}, 5 | translate::translate_data_type, 6 | }; 7 | 8 | #[derive(Clone, Debug)] 9 | pub enum DataTypeNode { 10 | DataType(DataType), 11 | Text(String), 12 | } 13 | 14 | impl From<DataType> for DataTypeNode { 15 | fn from(data_type: DataType) -> Self { 16 | Self::DataType(data_type) 17 | } 18 | } 19 | 20 | impl From<&str> for DataTypeNode { 21 | fn from(data_type: &str) -> Self { 22 | Self::Text(data_type.to_owned()) 23 | } 24 | } 25 | 26 | impl TryFrom<DataTypeNode> for DataType { 27 | type Error = Error; 28 | 29 | fn try_from(data_type: DataTypeNode) -> Result<Self> { 30 | match data_type { 31 | DataTypeNode::DataType(data_type) => Ok(data_type), 32 | DataTypeNode::Text(data_type) => { 33 | parse_data_type(data_type).and_then(|datatype| translate_data_type(&datatype)) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /core/src/ast_builder/error.rs: -------------------------------------------------------------------------------- 1 | use {serde::Serialize, std::fmt::Debug, thiserror::Error}; 2 | 3 | #[derive(Error, Serialize, Debug, PartialEq, Eq)] 4 | pub enum AstBuilderError { 5 | #[error("failed to parse numeric value: {0}")] 6 | FailedToParseNumeric(String), 7 | } 8 | -------------------------------------------------------------------------------- /core/src/ast_builder/execute.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::Build, 3 | crate::{ 4 | ast::Statement, 5 | executor::Payload, 6 | prelude::Glue, 7 | result::Result, 8 | store::{GStore, GStoreMut}, 9 | }, 10 | async_trait::async_trait, 11 | }; 12 | 13 | #[async_trait(?Send)] 14 | pub trait Execute<T: GStore + GStoreMut> 15 | where 16 | Self: Sized + Build, 17 | { 18 | async fn execute(self, glue: &mut Glue<T>) -> Result<Payload> { 19 | let statement = self.build()?; 20 | 21 | glue.execute_stmt(&statement).await 22 | } 23 | } 24 | 25 | #[async_trait(?Send)] 26 | impl<T: GStore + GStoreMut, B: Build> Execute<T> for B {} 27 | 28 | impl Build for Statement { 29 | fn build(self) -> Result<Statement> { 30 | Ok(self) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/src/ast_builder/expr/alias_as.rs: -------------------------------------------------------------------------------- 1 | use {super::ExprNode, crate::ast_builder::ExprWithAliasNode}; 2 | 3 | impl<'a> ExprNode<'a> { 4 | pub fn alias_as(self, alias: &str) -> ExprWithAliasNode<'a> { 5 | ExprWithAliasNode { 6 | expr: self, 7 | alias: alias.to_owned(), 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/src/ast_builder/expr/is_null.rs: -------------------------------------------------------------------------------- 1 | use super::ExprNode; 2 | 3 | impl<'a> ExprNode<'a> { 4 | pub fn is_null(self) -> Self { 5 | Self::IsNull(Box::new(self)) 6 | } 7 | 8 | pub fn is_not_null(self) -> Self { 9 | Self::IsNotNull(Box::new(self)) 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use crate::ast_builder::{col, num, test_expr}; 16 | 17 | #[test] 18 | fn is_null() { 19 | let actual = col("id").is_null(); 20 | let expected = "id IS NULL"; 21 | test_expr(actual, expected); 22 | 23 | let actual = num(10).add("id").is_not_null(); 24 | let expected = "10 + id IS NOT NULL"; 25 | test_expr(actual, expected); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/ast_builder/expr/nested.rs: -------------------------------------------------------------------------------- 1 | use super::ExprNode; 2 | 3 | impl<'a> ExprNode<'a> { 4 | pub fn nested(self) -> Self { 5 | nested(self) 6 | } 7 | } 8 | 9 | pub fn nested<'a, T: Into<ExprNode<'a>>>(expr: T) -> ExprNode<'a> { 10 | ExprNode::Nested(Box::new(expr.into())) 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use crate::ast_builder::{col, nested, test_expr}; 16 | 17 | #[test] 18 | fn test_nested() { 19 | let actual = col("val1").add(col("val2")).nested(); 20 | let expected = "(val1 + val2)"; 21 | test_expr(actual, expected); 22 | 23 | let actual = nested(col("val1").add(col("val2"))); 24 | let expected = "(val1 + val2)"; 25 | test_expr(actual, expected); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/ast_builder/expr_with_alias.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ast::Expr, 3 | ast_builder::ExprNode, 4 | result::{Error, Result}, 5 | }; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct ExprWithAliasNode<'a> { 9 | pub expr: ExprNode<'a>, 10 | pub alias: String, 11 | } 12 | 13 | impl<'a> TryFrom<ExprWithAliasNode<'a>> for (Expr, String) { 14 | type Error = Error; 15 | 16 | fn try_from(node: ExprWithAliasNode<'a>) -> Result<Self> { 17 | Ok((Expr::try_from(node.expr)?, node.alias)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/src/ast_builder/index_item/primary_key.rs: -------------------------------------------------------------------------------- 1 | use {super::IndexItemNode, crate::ast_builder::ExprNode}; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct PrimaryKeyNode; 5 | 6 | impl<'a> PrimaryKeyNode { 7 | pub fn eq<T: Into<ExprNode<'a>>>(self, expr: T) -> IndexItemNode<'a> { 8 | IndexItemNode::PrimaryKey(expr.into()) 9 | } 10 | } 11 | 12 | /// Entry point function to Primary Key 13 | pub fn primary_key() -> PrimaryKeyNode { 14 | PrimaryKeyNode 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use crate::{ 20 | ast::{AstLiteral, Expr}, 21 | ast_builder::{index_item::IndexItem, primary_key, select::Prebuild}, 22 | }; 23 | 24 | #[test] 25 | fn test() { 26 | let actual = primary_key().eq("1").prebuild().unwrap(); 27 | let expected = IndexItem::PrimaryKey(Expr::Literal(AstLiteral::Number(1.into()))); 28 | assert_eq!(actual, expected); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/ast_builder/select/join.rs: -------------------------------------------------------------------------------- 1 | mod hash_join; 2 | mod join_constraint; 3 | mod root; 4 | 5 | pub use {hash_join::HashJoinNode, join_constraint::JoinConstraintNode, root::JoinNode}; 6 | 7 | use crate::ast::{JoinConstraint, JoinExecutor, JoinOperator, Select, TableFactor}; 8 | 9 | #[derive(Clone, Copy, Debug)] 10 | pub enum JoinOperatorType { 11 | Inner, 12 | Left, 13 | } 14 | 15 | impl From<JoinOperatorType> for JoinOperator { 16 | fn from(join_operator_type: JoinOperatorType) -> Self { 17 | match join_operator_type { 18 | JoinOperatorType::Inner => JoinOperator::Inner(JoinConstraint::None), 19 | JoinOperatorType::Left => JoinOperator::LeftOuter(JoinConstraint::None), 20 | } 21 | } 22 | } 23 | 24 | pub struct JoinConstraintData { 25 | select: Select, 26 | relation: TableFactor, 27 | operator_type: JoinOperatorType, 28 | executor: JoinExecutor, 29 | } 30 | -------------------------------------------------------------------------------- /core/src/ast_builder/show_columns.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::Build, 3 | crate::{ast::Statement, result::Result}, 4 | }; 5 | 6 | #[derive(Clone, Debug)] 7 | pub struct ShowColumnsNode { 8 | table_name: String, 9 | } 10 | 11 | impl ShowColumnsNode { 12 | pub fn new(table_name: String) -> Self { 13 | Self { table_name } 14 | } 15 | } 16 | 17 | impl Build for ShowColumnsNode { 18 | fn build(self) -> Result<Statement> { 19 | let table_name = self.table_name; 20 | Ok(Statement::ShowColumns { table_name }) 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | use crate::ast_builder::{Build, table, test}; 27 | 28 | #[test] 29 | fn show_columns() { 30 | let actual = table("Foo").show_columns().build(); 31 | let expected = "SHOW COLUMNS FROM Foo"; 32 | test(actual, expected); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/ast_builder/transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::Statement; 2 | 3 | pub fn begin() -> Statement { 4 | Statement::StartTransaction 5 | } 6 | pub fn commit() -> Statement { 7 | Statement::Commit 8 | } 9 | pub fn rollback() -> Statement { 10 | Statement::Rollback 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use crate::ast_builder::{begin, commit, rollback, test}; 16 | 17 | #[test] 18 | fn transaction() { 19 | let actual = begin(); 20 | let expected = "START TRANSACTION"; 21 | test(Ok(actual), expected); 22 | 23 | let actual = commit(); 24 | let expected = "COMMIT"; 25 | test(Ok(actual), expected); 26 | 27 | let actual = rollback(); 28 | let expected = "ROLLBACK"; 29 | test(Ok(actual), expected); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/src/data.rs: -------------------------------------------------------------------------------- 1 | mod bigdecimal_ext; 2 | mod function; 3 | mod interval; 4 | mod key; 5 | mod literal; 6 | mod point; 7 | mod row; 8 | mod string_ext; 9 | mod table; 10 | 11 | pub mod schema; 12 | pub mod value; 13 | 14 | pub use { 15 | bigdecimal_ext::BigDecimalExt, 16 | function::CustomFunction, 17 | interval::{Interval, IntervalError}, 18 | key::{Key, KeyError}, 19 | literal::{Literal, LiteralError}, 20 | point::Point, 21 | row::{Row, RowError}, 22 | schema::{Schema, SchemaIndex, SchemaIndexOrd, SchemaParseError}, 23 | string_ext::{StringExt, StringExtError}, 24 | table::{TableError, get_alias, get_index}, 25 | value::{ConvertError, HashMapJsonExt, NumericBinaryOperator, Value, ValueError}, 26 | }; 27 | -------------------------------------------------------------------------------- /core/src/data/function.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::ast::{Expr, OperateFunctionArg}, 3 | serde::{Deserialize, Serialize}, 4 | }; 5 | 6 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 7 | pub struct CustomFunction { 8 | pub func_name: String, 9 | pub args: Vec<OperateFunctionArg>, 10 | pub body: Expr, 11 | } 12 | 13 | impl CustomFunction { 14 | pub fn to_str(&self) -> String { 15 | let name = &self.func_name; 16 | let args = self 17 | .args 18 | .iter() 19 | .map(|arg| format!("{}: {}", arg.name, arg.data_type)) 20 | .collect::<Vec<String>>() 21 | .join(", "); 22 | format!("{name}({args})") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/data/string_ext.rs: -------------------------------------------------------------------------------- 1 | use {crate::result::Result, regex::Regex, serde::Serialize, thiserror::Error}; 2 | 3 | #[derive(Error, Serialize, Debug, PartialEq, Eq)] 4 | pub enum StringExtError { 5 | #[error("unreachable literal unary operation")] 6 | UnreachablePatternParsing, 7 | } 8 | 9 | pub trait StringExt { 10 | fn like(&self, pattern: &str, case_sensitive: bool) -> Result<bool>; 11 | } 12 | 13 | impl StringExt for str { 14 | fn like(&self, pattern: &str, case_sensitive: bool) -> Result<bool> { 15 | let (match_string, match_pattern) = match case_sensitive { 16 | true => (self.to_owned(), pattern.to_owned()), 17 | false => { 18 | let lowercase_string = self.to_lowercase(); 19 | let lowercase_pattern = pattern.to_lowercase(); 20 | 21 | (lowercase_string, lowercase_pattern) 22 | } 23 | }; 24 | 25 | Ok(Regex::new(&format!( 26 | "^{}quot;, 27 | regex::escape(match_pattern.as_str()) 28 | .replace('%', ".*") 29 | .replace('_', ".") 30 | )) 31 | .map_err(|_| StringExtError::UnreachablePatternParsing)? 32 | .is_match(match_string.as_str())) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/data/table.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::ast::{IndexItem, TableAlias, TableFactor}, 3 | serde::Serialize, 4 | std::fmt::Debug, 5 | thiserror::Error, 6 | }; 7 | 8 | #[derive(Error, Serialize, Debug, PartialEq, Eq)] 9 | pub enum TableError { 10 | #[error("unreachable")] 11 | Unreachable, 12 | } 13 | 14 | pub fn get_alias(table_factor: &TableFactor) -> &String { 15 | match table_factor { 16 | TableFactor::Table { 17 | name, alias: None, .. 18 | } 19 | | TableFactor::Table { 20 | alias: Some(TableAlias { name, .. }), 21 | .. 22 | } 23 | | TableFactor::Derived { 24 | alias: TableAlias { name, .. }, 25 | .. 26 | } 27 | | TableFactor::Series { 28 | alias: TableAlias { name, .. }, 29 | .. 30 | } 31 | | TableFactor::Dictionary { 32 | alias: TableAlias { name, .. }, 33 | .. 34 | } => name, 35 | } 36 | } 37 | 38 | pub fn get_index(table_factor: &TableFactor) -> Option<&IndexItem> { 39 | match table_factor { 40 | TableFactor::Table { index, .. } => index.as_ref(), 41 | TableFactor::Derived { .. } 42 | | TableFactor::Series { .. } 43 | | TableFactor::Dictionary { .. } => None, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op.rs: -------------------------------------------------------------------------------- 1 | use crate::{prelude::Value, result::Result}; 2 | 3 | mod decimal; 4 | mod f32; 5 | mod f64; 6 | 7 | mod integer; 8 | 9 | pub trait TryBinaryOperator { 10 | type Rhs; 11 | 12 | fn try_add(&self, rhs: &Self::Rhs) -> Result<Value>; 13 | fn try_subtract(&self, rhs: &Self::Rhs) -> Result<Value>; 14 | fn try_multiply(&self, rhs: &Self::Rhs) -> Result<Value>; 15 | fn try_divide(&self, rhs: &Self::Rhs) -> Result<Value>; 16 | fn try_modulo(&self, rhs: &Self::Rhs) -> Result<Value>; 17 | } 18 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer.rs: -------------------------------------------------------------------------------- 1 | mod i128; 2 | mod i16; 3 | mod i32; 4 | mod i64; 5 | mod i8; 6 | mod u128; 7 | mod u16; 8 | mod u32; 9 | mod u64; 10 | mod u8; 11 | 12 | mod macros; 13 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/i128.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(I128, i128); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(I128, i128); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(i128); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(i128); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/i16.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(I16, i16); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(I16, i16); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(i16); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(i16); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/i32.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(I32, i32); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(I32, i32); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(i32); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(i32); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/i64.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(I64, i64); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(I64, i64); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(i64); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(i64); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/i8.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(I8, i8); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(I8, i8); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(i8); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(i8); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/u128.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(U128, u128); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(U128, u128); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(u128); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(u128); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/u16.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(U16, u16); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(U16, u16); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(u16); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(u16); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/u32.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(U32, u32); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(U32, u32); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(u32); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(u32); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/u64.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(U64, u64); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(U64, u64); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(u64); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(u64); 10 | -------------------------------------------------------------------------------- /core/src/data/value/binary_op/integer/u8.rs: -------------------------------------------------------------------------------- 1 | use {crate::prelude::Value, std::cmp::Ordering}; 2 | 3 | super::macros::impl_try_binary_op!(U8, u8); 4 | #[cfg(test)] 5 | super::macros::generate_binary_op_tests!(U8, u8); 6 | 7 | super::macros::impl_partial_cmp_ord_method!(u8); 8 | #[cfg(test)] 9 | super::macros::generate_cmp_ord_tests!(u8); 10 | -------------------------------------------------------------------------------- /core/src/data/value/uuid.rs: -------------------------------------------------------------------------------- 1 | use {super::ValueError, crate::result::Result, uuid::Uuid}; 2 | 3 | pub fn parse_uuid(v: &str) -> Result<u128> { 4 | match Uuid::parse_str(v) { 5 | Ok(u) => Ok(u.as_u128()), 6 | _ => Err(ValueError::FailedToParseUUID(v.to_owned()).into()), 7 | } 8 | } 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use crate::data::value::ValueError; 13 | 14 | #[test] 15 | fn parse_uuid() { 16 | macro_rules! test ( 17 | ($str: literal, $result: expr) => { 18 | assert_eq!(super::parse_uuid($str), $result) 19 | } 20 | ); 21 | 22 | test!( 23 | "936DA01F9ABD4d9d80C702AF85C822A8", 24 | Ok(195965723427462096757863453463987888808) 25 | ); 26 | test!( 27 | "550e8400-e29b-41d4-a716-446655440000", 28 | Ok(113059749145936325402354257176981405696) 29 | ); 30 | test!( 31 | "urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4", 32 | Ok(331094848530093083170738142201201533412) 33 | ); 34 | 35 | test!( 36 | "1", 37 | Err(ValueError::FailedToParseUUID("1".to_owned()).into()) 38 | ); 39 | test!( 40 | "NOT_UUID_STRING", 41 | Err(ValueError::FailedToParseUUID("NOT_UUID_STRING".to_owned()).into()) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/executor.rs: -------------------------------------------------------------------------------- 1 | mod aggregate; 2 | mod alter; 3 | mod context; 4 | mod delete; 5 | mod evaluate; 6 | mod execute; 7 | mod fetch; 8 | mod filter; 9 | mod insert; 10 | mod join; 11 | mod limit; 12 | mod select; 13 | mod sort; 14 | mod update; 15 | mod validate; 16 | 17 | pub use { 18 | alter::{AlterError, Referencing}, 19 | context::RowContext, 20 | delete::DeleteError, 21 | evaluate::{EvaluateError, evaluate_stateless}, 22 | execute::{ExecuteError, Payload, PayloadVariable, execute}, 23 | fetch::FetchError, 24 | insert::InsertError, 25 | select::SelectError, 26 | sort::SortError, 27 | update::UpdateError, 28 | validate::ValidateError, 29 | }; 30 | -------------------------------------------------------------------------------- /core/src/executor/alter.rs: -------------------------------------------------------------------------------- 1 | mod alter_table; 2 | mod error; 3 | mod function; 4 | mod index; 5 | mod table; 6 | mod validate; 7 | 8 | use validate::{validate, validate_arg_names, validate_column_names, validate_default_args}; 9 | 10 | pub use { 11 | alter_table::alter_table, 12 | error::AlterError, 13 | function::{delete_function, insert_function}, 14 | index::create_index, 15 | table::{CreateTableOptions, Referencing, create_table, drop_table}, 16 | }; 17 | -------------------------------------------------------------------------------- /core/src/executor/context.rs: -------------------------------------------------------------------------------- 1 | mod aggregate_context; 2 | mod row_context; 3 | 4 | pub use {aggregate_context::AggregateContext, row_context::RowContext}; 5 | -------------------------------------------------------------------------------- /core/src/executor/context/aggregate_context.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::RowContext, 3 | crate::{ast::Aggregate, data::Value}, 4 | im_rc::HashMap, 5 | std::{fmt::Debug, rc::Rc}, 6 | }; 7 | 8 | #[derive(Debug)] 9 | pub struct AggregateContext<'a> { 10 | pub aggregated: Option<HashMap<&'a Aggregate, Value>>, 11 | pub next: Rc<RowContext<'a>>, 12 | } 13 | -------------------------------------------------------------------------------- /core/src/executor/select/error.rs: -------------------------------------------------------------------------------- 1 | use {serde::Serialize, std::fmt::Debug, thiserror::Error}; 2 | 3 | #[derive(Error, Serialize, Debug, PartialEq, Eq)] 4 | pub enum SelectError { 5 | #[error("VALUES lists must all be the same length")] 6 | NumberOfValuesDifferent, 7 | } 8 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::str_to_string)] 2 | 3 | // re-export 4 | pub use {chrono, sqlparser}; 5 | 6 | mod glue; 7 | mod mock; 8 | mod result; 9 | 10 | pub mod ast; 11 | pub mod ast_builder; 12 | pub mod data; 13 | pub mod executor; 14 | pub mod parse_sql; 15 | pub mod plan; 16 | pub mod store; 17 | pub mod translate; 18 | 19 | pub mod prelude { 20 | pub use crate::{ 21 | ast::DataType, 22 | data::{Key, Value}, 23 | executor::{Payload, PayloadVariable, execute}, 24 | glue::Glue, 25 | parse_sql::parse, 26 | plan::plan, 27 | result::{Error, Result}, 28 | translate::translate, 29 | }; 30 | } 31 | 32 | pub mod error { 33 | pub use crate::result::*; 34 | } 35 | -------------------------------------------------------------------------------- /core/src/plan.rs: -------------------------------------------------------------------------------- 1 | mod context; 2 | mod error; 3 | mod evaluable; 4 | mod expr; 5 | mod index; 6 | mod join; 7 | mod planner; 8 | mod primary_key; 9 | mod schema; 10 | mod validate; 11 | 12 | use crate::{ast::Statement, result::Result, store::Store}; 13 | 14 | pub use { 15 | self::validate::validate, error::*, index::plan as plan_index, join::plan as plan_join, 16 | primary_key::plan as plan_primary_key, schema::fetch_schema_map, 17 | }; 18 | 19 | pub async fn plan<T: Store>(storage: &T, statement: Statement) -> Result<Statement> { 20 | let schema_map = fetch_schema_map(storage, &statement).await?; 21 | validate(&schema_map, &statement)?; 22 | let statement = plan_primary_key(&schema_map, statement); 23 | let statement = plan_index(&schema_map, statement)?; 24 | let statement = plan_join(&schema_map, statement); 25 | 26 | Ok(statement) 27 | } 28 | -------------------------------------------------------------------------------- /core/src/plan/error.rs: -------------------------------------------------------------------------------- 1 | use {serde::Serialize, std::fmt::Debug, thiserror::Error as ThisError}; 2 | 3 | #[derive(ThisError, Serialize, Debug, PartialEq, Eq)] 4 | pub enum PlanError { 5 | /// Error that that omits when user projects common column name from multiple tables in `JOIN` 6 | /// situation. 7 | #[error("column reference {0} is ambiguous, please specify the table name")] 8 | ColumnReferenceAmbiguous(String), 9 | } 10 | -------------------------------------------------------------------------------- /core/src/store/function.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{ 3 | data::CustomFunction as StructCustomFunction, 4 | result::{Error, Result}, 5 | }, 6 | async_trait::async_trait, 7 | }; 8 | 9 | #[async_trait(?Send)] 10 | pub trait CustomFunction { 11 | async fn fetch_function<'a>( 12 | &'a self, 13 | _func_name: &str, 14 | ) -> Result<Option<&'a StructCustomFunction>> { 15 | Err(Error::StorageMsg( 16 | "[Storage] CustomFunction is not supported".to_owned(), 17 | )) 18 | } 19 | 20 | async fn fetch_all_functions<'a>(&'a self) -> Result<Vec<&'a StructCustomFunction>> { 21 | Err(Error::StorageMsg( 22 | "[Storage] CustomFunction is not supported".to_owned(), 23 | )) 24 | } 25 | } 26 | 27 | #[async_trait(?Send)] 28 | pub trait CustomFunctionMut { 29 | async fn insert_function(&mut self, _func: StructCustomFunction) -> Result<()> { 30 | Err(Error::StorageMsg( 31 | "[Storage] CustomFunction is not supported".to_owned(), 32 | )) 33 | } 34 | 35 | async fn delete_function(&mut self, _func_name: &str) -> Result<()> { 36 | Err(Error::StorageMsg( 37 | "[Storage] CustomFunction is not supported".to_owned(), 38 | )) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/store/metadata.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{prelude::Value, result::Result}, 3 | async_trait::async_trait, 4 | std::{collections::HashMap, iter::empty}, 5 | }; 6 | 7 | type ObjectName = String; 8 | pub type MetaIter = Box<dyn Iterator<Item = Result<(ObjectName, HashMap<String, Value>)>>>; 9 | 10 | #[async_trait(?Send)] 11 | pub trait Metadata { 12 | async fn scan_table_meta(&self) -> Result<MetaIter> { 13 | Ok(Box::new(empty())) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/src/store/transaction.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::result::{Error, Result}, 3 | async_trait::async_trait, 4 | }; 5 | 6 | #[async_trait(?Send)] 7 | pub trait Transaction { 8 | async fn begin(&mut self, autocommit: bool) -> Result<bool> { 9 | if autocommit { 10 | return Ok(false); 11 | } 12 | 13 | Err(Error::StorageMsg( 14 | "[Storage] Transaction::begin is not supported".to_owned(), 15 | )) 16 | } 17 | 18 | async fn rollback(&mut self) -> Result<()> { 19 | Ok(()) 20 | } 21 | 22 | async fn commit(&mut self) -> Result<()> { 23 | Ok(()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # GlueSQL Docs 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ npm install 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ npm run start 15 | $ npm run start:blog 16 | ``` 17 | 18 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 19 | 20 | ### Build 21 | 22 | ``` 23 | $ npm run build 24 | $ npm run build:blog 25 | ``` 26 | 27 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 28 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/blog/assets/blog-test-driven-documentation-insert-errorcase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/docs/blog/assets/blog-test-driven-documentation-insert-errorcase.jpg -------------------------------------------------------------------------------- /docs/blog/assets/blog-test-driven-documentation-insert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/docs/blog/assets/blog-test-driven-documentation-insert.jpg -------------------------------------------------------------------------------- /docs/docs/ast-builder/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "AST Builder", 3 | "position": 4, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/expressions/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Expressions", 3 | "position": 3, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/expressions/conditional.md: -------------------------------------------------------------------------------- 1 | # Conditional 2 | 3 | ## Todo 4 | 5 | - CASE: Returns a value on a condition. 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/expressions/nested.md: -------------------------------------------------------------------------------- 1 | # Nested 2 | 3 | ## Todo 4 | 5 | - NESTED: Represents a nested expression, allowing the use of complex and grouped expressions. 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/expressions/operator-based.md: -------------------------------------------------------------------------------- 1 | # Operator Based 2 | 3 | ## Todo 4 | 5 | - BINARY_OP: Represents a binary operation (like +, -, *, /, etc.). 6 | - UNARY_OP: Represents a unary operation (like NOT, -, etc.). 7 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/expressions/value-checking.md: -------------------------------------------------------------------------------- 1 | # Value Checking 2 | 3 | ## Todo 4 | 5 | - BETWEEN: Checks if a value is within a range of values. 6 | - IN_LIST: Checks if a value is within a list of values. 7 | - IS_NULL: Checks if a value is NULL. 8 | - EXISTS: Checks if a subquery returns any rows. 9 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Functions", 3 | "position": 4, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/date-&-time/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Date & Time", 3 | "position": 3, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/date-&-time/current-date-and-time.md: -------------------------------------------------------------------------------- 1 | # Current Date and Time 2 | 3 | GlueSQL provides a function to get the current date and time: `now`. 4 | 5 | ## Now - now 6 | 7 | The `now` function returns the current date and time. 8 | 9 | ```rust 10 | let actual = table("Record") 11 | .select() 12 | .filter(col("time_stamp").gt(now())) // select rows where "time_stamp" is later than current time 13 | .project("id, time_stamp") 14 | .execute(glue) 15 | .await; 16 | ``` 17 | 18 | In the above example, the `filter` method uses `now` to select rows where the "time_stamp" column is later than the current time. 19 | 20 | When inserting data into a table, you can use the `now` function to record the current time: 21 | 22 | ```rust 23 | let actual = table("Record") 24 | .insert() 25 | .values(vec![ 26 | "1, '2022-12-23T05:30:11.164932863'", 27 | "2, NOW()", // Inserts the current time 28 | "3, '9999-12-31T23:59:40.364832862'", 29 | ]) 30 | .execute(glue) 31 | .await; 32 | ``` 33 | In the example above, the "time_stamp" column for the row with id 2 is set to the current time at the moment of insertion. 34 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/date-&-time/date-and-time-extraction.md: -------------------------------------------------------------------------------- 1 | # Date and Time Extraction 2 | 3 | ## Todo 4 | 5 | - EXTRACT: Extracts a part of a date or time (like day, month, year, hour, minute, etc.). 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/geometry/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Geometry", 3 | "position": 5, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/geometry/coordinate-extraction.md: -------------------------------------------------------------------------------- 1 | # Coordinate Extraction 2 | 3 | ## Todo 4 | 5 | - GET_X: Extracts the x-coordinate from a point. 6 | - GET_Y: Extracts the y-coordinate from a point. 7 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/geometry/distance-calculation.md: -------------------------------------------------------------------------------- 1 | # Distance Calculation 2 | 3 | ## Todo 4 | 5 | - CALC_DISTANCE: Calculates the distance between two points. 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/geometry/point-creation.md: -------------------------------------------------------------------------------- 1 | # Point Creation 2 | 3 | ## Todo 4 | 5 | - POINT: Creates a geometric point with an x and y coordinate. 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/list-&-map/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "List & Map", 3 | "position": 4, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/list-&-map/list-and-map-concatenation.md: -------------------------------------------------------------------------------- 1 | # List and Map Concatenation 2 | 3 | ## Todo 4 | 5 | - CONCAT: Combines two or more lists or maps into one. 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/list-&-map/list-manipulation.md: -------------------------------------------------------------------------------- 1 | # List Manipulation 2 | 3 | ## Todo 4 | 5 | - APPEND: Adds an element to the end of a list. 6 | - PREPEND: Adds an element to the beginning of a list. 7 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/math/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Math", 3 | "position": 2, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/math/logarithmic-and-exponential.md: -------------------------------------------------------------------------------- 1 | # Logarithmic and Exponential 2 | ## Todo 3 | - EXP: Returns e raised to the power of a specified number. 4 | - LN: Returns the natural logarithm of a number. 5 | - LOG: Returns the logarithm of a number to a specified base. 6 | - LOG10: Returns the base-10 logarithm of a number. 7 | - LOG2: Returns the base-2 logarithm of a number. 8 | - POWER: Raises a number to the power of another number. 9 | 10 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/math/special-mathematical.md: -------------------------------------------------------------------------------- 1 | # Special Mathematical 2 | ## Todo 3 | - PI: Returns the constant value of Pi. 4 | - RAND: Returns a random number. 5 | - SIGN: Returns the sign of a number. 6 | - SQRT: Returns the square root of a number. 7 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/math/trigonometric.md: -------------------------------------------------------------------------------- 1 | # Trigonometric 2 | ## Todo 3 | - ACOS: Returns the arc cosine of a number. 4 | - ASIN: Returns the arc sine of a number. 5 | - ATAN: Returns the arc tangent of a number. 6 | - COS: Returns the cosine of a number. 7 | - SIN: Returns the sine of a number. 8 | - TAN: Returns the tangent of a number. 9 | 10 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/others/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Others", 3 | "position": 6, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/others/type-conversion.md: -------------------------------------------------------------------------------- 1 | # Type Conversion 2 | ## Todo 3 | - CAST: Converts a value from one data type to another. 4 | 5 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/others/unique-identifier.md: -------------------------------------------------------------------------------- 1 | # Unique Identifier 2 | ## Todo 3 | - GENERATE_UUID: Generates a universally unique identifier (UUID). 4 | 5 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/text/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Text", 3 | "position": 1, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/functions/text/text-manipulation.md: -------------------------------------------------------------------------------- 1 | # Text Manipulation 2 | ## Todo 3 | - CONCAT: Concatenates two or more strings into one. 4 | - CONCAT_WS: Concatenates two or more strings into one with a separator. 5 | - SUBSTR: Returns a part of a string. 6 | - REPEAT: Repeats a string a specified number of times. 7 | - REVERSE: Reverses the order of the characters in a string. 8 | 9 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/statements/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Statements", 3 | "position": 1, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/statements/data-manipulation/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Data Manipulation", 3 | "position": 2, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/statements/data-manipulation/deleting-data.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Deleting Data 6 | 7 | In this section, we will discuss how to delete data from a table using GlueSQL. 8 | 9 | ## Delete with Filter 10 | 11 | To delete specific rows from a table, you can use the `delete` method on a table object, followed by the `filter` method to provide a condition that the rows must meet. You can then use the `execute` method to apply the changes. 12 | 13 | ```rust 14 | let actual = table("Foo") 15 | .delete() 16 | .filter(col("flag").eq(false)) 17 | .execute(glue) 18 | .await; 19 | let expected = Ok(Payload::Delete(1)); 20 | test(actual, expected); 21 | ``` 22 | 23 | This code deletes the rows in the table `Foo` where the `flag` column value is false. 24 | 25 | ## Delete All Rows 26 | 27 | To delete all rows from a table, you can use the `delete` method on a table object, followed by the `execute` method. 28 | 29 | ```rust 30 | let actual = table("Foo").delete().execute(glue).await; 31 | let expected = Ok(Payload::Delete(2)); 32 | test(actual, expected); 33 | ``` 34 | 35 | This code deletes all rows from the table `Foo`. 36 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/statements/querying/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Querying", 3 | "position": 1, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/statements/querying/data-injection.md: -------------------------------------------------------------------------------- 1 | # Data Injection 2 | ## Todo 3 | - VALUES: Allows users to manually input data into the query. 4 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/statements/querying/data-joining.md: -------------------------------------------------------------------------------- 1 | # Data Joining 2 | ## Todo 3 | - JOIN: Combines rows from two or more tables based on a related column between them. 4 | 5 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/statements/querying/data-selection-and-projection.md: -------------------------------------------------------------------------------- 1 | # Data Selection and Projection 2 | ## Todo 3 | - FILTER: Filters rows in a dataset based on specified conditions. 4 | - PROJECT: Selects which columns to include in the result set. 5 | 6 | -------------------------------------------------------------------------------- /docs/docs/ast-builder/statements/querying/data-sorting-and-limiting.md: -------------------------------------------------------------------------------- 1 | # Data Sorting and Limiting 2 | ## Todo 3 | - ORDER_BY: Sorts the result set in ascending or descending order based on specified column(s). 4 | - LIMIT: Limits the number of rows returned in the result set. 5 | - OFFSET: Skips a specified number of rows in the result set. 6 | 7 | -------------------------------------------------------------------------------- /docs/docs/getting-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Getting Started", 3 | "position": 2, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "SQL Syntax", 3 | "position": 3, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/data-types/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Data types", 3 | "position": 2, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Functions", 3 | "position": 3, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/datetime/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Date & Time", 3 | "position": 3, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/datetime/add-month.md: -------------------------------------------------------------------------------- 1 | # ADD_MONTH 2 | 3 | `ADD_MONTH` shifts a date by the given number of months. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | ADD_MONTH(date, months) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `date` – A `DATE` value. 14 | - `months` – Integer number of months to add. Negative values subtract months. 15 | 16 | ## Examples 17 | 18 | ```sql 19 | SELECT ADD_MONTH('2017-06-15', 1) AS next_month; 20 | SELECT ADD_MONTH('2017-06-15', -1) AS prev_month; 21 | ``` 22 | 23 | These return `2017-07-15` and `2017-05-15` respectively. 24 | 25 | ## Notes 26 | 27 | If the resulting day does not exist in the target month, the last day of that month is used. 28 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/datetime/last-day.md: -------------------------------------------------------------------------------- 1 | # LAST_DAY 2 | 3 | The `LAST_DAY` function returns the last day of the month of a given date or timestamp. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | LAST_DAY(value) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `value` – A `DATE` or `TIMESTAMP` expression. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT LAST_DAY('2017-12-15'); 19 | ``` 20 | 21 | This returns `2017-12-31`. 22 | 23 | ## Notes 24 | 25 | `LAST_DAY` accepts only date or timestamp values; other types produce an error. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/datetime/now.md: -------------------------------------------------------------------------------- 1 | # NOW 2 | 3 | The `NOW()` function in SQL returns the current date and time in UTC. You can use it to retrieve the current UTC timestamp, or as a default value for a TIMESTAMP column in a table. 4 | 5 | ## Syntax 6 | 7 | ``` 8 | NOW() 9 | ``` 10 | 11 | ## Examples 12 | 13 | ### Creating a table with a TIMESTAMP column and setting the default value to NOW() 14 | 15 | ```sql 16 | CREATE TABLE Item (time TIMESTAMP DEFAULT NOW()); 17 | ``` 18 | 19 | This creates a table named `Item` with a column `time` of the type TIMESTAMP. The default value for this column is the current UTC timestamp. 20 | 21 | ### Inserting data into the table 22 | 23 | ```sql 24 | INSERT INTO Item (time) VALUES 25 | ('2021-10-13T06:42:40.364832862'), 26 | ('9999-12-31T23:59:40.364832862'); 27 | ``` 28 | 29 | Here we're inserting two rows into the `Item` table with specific timestamps. 30 | 31 | ### Selecting rows where the timestamp is greater than the current timestamp 32 | 33 | ```sql 34 | SELECT time FROM Item WHERE time > NOW(); 35 | ``` 36 | 37 | This query selects the `time` column from the `Item` table where the `time` is greater than the current UTC timestamp. In this case, the result will be: 38 | 39 | ``` 40 | 9999-12-31T23:59:40.364832862 41 | ``` -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/geometry/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Geometry", 3 | "position": 5, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/geometry/calc-distance.md: -------------------------------------------------------------------------------- 1 | # CALC_DISTANCE 2 | 3 | The `CALC_DISTANCE` function is used to calculate the Euclidean distance between two `Point` type geographical coordinates. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | CALC_DISTANCE(point1, point2) 9 | ``` 10 | 11 | **Parameters:** 12 | 13 | - `point1`: The first geographical coordinate of type `Point`. 14 | - `point2`: The second geographical coordinate of type `Point`. 15 | 16 | ## Examples 17 | 18 | Consider the following table `Foo`: 19 | 20 | ```sql 21 | CREATE TABLE Foo ( 22 | geo1 Point, 23 | geo2 Point, 24 | bar Float 25 | ); 26 | ``` 27 | 28 | With the following data: 29 | 30 | ```sql 31 | INSERT INTO Foo VALUES (POINT(0.3134, 3.156), POINT(1.415, 3.231), 3); 32 | ``` 33 | 34 | ### Example 1: Calculate the distance between two points 35 | 36 | ```sql 37 | SELECT CALC_DISTANCE(geo1, geo2) AS georesult FROM Foo; 38 | ``` 39 | 40 | **Result:** 41 | 42 | | georesult | 43 | |-----------------| 44 | | 1.104150152832485| 45 | 46 | ## Errors 47 | 48 | 1. If the number of arguments is not 2, a `FunctionArgsLengthNotMatching` error will be thrown. 49 | 2. If any of the arguments are not of type `Point`, a `FunctionRequiresPointValue` error will be thrown. 50 | 3. If any of the arguments are `NULL`, the result will be `NULL`. 51 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/geometry/point.md: -------------------------------------------------------------------------------- 1 | 2 | # POINT 3 | 4 | The `POINT` function creates a point value using the provided x and y coordinates. A point value represents a two-dimensional geometric point with a pair of floating-point numbers, often used for storing spatial data. 5 | 6 | ## Syntax 7 | 8 | ```sql 9 | POINT(x, y) 10 | ``` 11 | 12 | ## Examples 13 | 14 | Create a table with a `POINT` data type column: 15 | 16 | ```sql 17 | CREATE TABLE Foo (point_field POINT); 18 | ``` 19 | 20 | Insert a record with a point value: 21 | 22 | ```sql 23 | INSERT INTO Foo VALUES (POINT(0.3134, 0.156)); 24 | ``` 25 | 26 | Select the `point_field` column: 27 | 28 | ```sql 29 | SELECT point_field AS point_field FROM Foo; 30 | ``` 31 | 32 | Update the `point_field` column: 33 | 34 | ```sql 35 | UPDATE Foo SET point_field = POINT(2.0, 1.0) WHERE point_field = POINT(0.3134, 0.156); 36 | ``` 37 | 38 | Select the updated `point_field` column: 39 | 40 | ```sql 41 | SELECT point_field AS point_field FROM Foo; 42 | ``` 43 | 44 | Delete the record with the specified point value: 45 | 46 | ```sql 47 | DELETE FROM Foo WHERE point_field = POINT(2.0, 1.0); 48 | ``` 49 | 50 | Casting a string to a `POINT`: 51 | 52 | ```sql 53 | SELECT CAST('POINT(-71.064544 42.28787)' AS POINT) AS pt; 54 | ``` -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "List & Map", 3 | "position": 4, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/concat.md: -------------------------------------------------------------------------------- 1 | # CONCAT 2 | 3 | The `CONCAT` function is used to concatenate two or more list values together. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | CONCAT(list_value1, list_value2, ...) 9 | ``` 10 | 11 | **Parameters:** 12 | 13 | - `list_value1`, `list_value2`, ...: List values that will be concatenated. 14 | 15 | ## Examples 16 | 17 | ### Example: CONCAT two lists 18 | 19 | ```sql 20 | SELECT CONCAT( 21 | CAST('[1, 2, 3]' AS LIST), 22 | CAST('["one", "two", "three"]' AS LIST) 23 | ) AS myconcat; 24 | ``` 25 | 26 | **Result:** 27 | 28 | | myconcat | 29 | |-------------------------------------| 30 | | [1, 2, 3, "one", "two", "three"] | 31 | 32 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/dedup.md: -------------------------------------------------------------------------------- 1 | # DEDUP 2 | 3 | `DEDUP` removes duplicate elements from a list while preserving order. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | DEDUP(list) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `list` – List value to process. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT DEDUP(CAST('[1, 2, 3, 3, 4, 5, 5]' AS LIST)); 19 | ``` 20 | 21 | This returns `[1, 2, 3, 4, 5]`. 22 | 23 | ## Notes 24 | 25 | A non-list argument results in an error. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/entries.md: -------------------------------------------------------------------------------- 1 | # ENTRIES 2 | 3 | `ENTRIES` converts a map into a list of key–value pairs. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | ENTRIES(map) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `map` – Map expression to convert. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT ENTRIES(CAST('{"name":"GlueSQL"}' AS MAP)); 19 | ``` 20 | 21 | This returns `[["name", "GlueSQL"]]`. 22 | 23 | ## Notes 24 | 25 | `ENTRIES` requires a map argument. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/is-empty.md: -------------------------------------------------------------------------------- 1 | # IS_EMPTY 2 | 3 | `IS_EMPTY` checks whether a list or map contains no elements. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | IS_EMPTY(value) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `value` – List or map expression. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT IS_EMPTY(CAST('[]' AS LIST)); -- true 19 | SELECT IS_EMPTY(CAST('{"a":1}' AS MAP)); -- false 20 | ``` 21 | 22 | ## Notes 23 | 24 | Using other data types results in an error. 25 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/keys.md: -------------------------------------------------------------------------------- 1 | # KEYS 2 | 3 | `KEYS` returns the keys of a map as a list. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | KEYS(map) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `map` – Map expression. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT KEYS(CAST('{"id":1, "name":"alice"}' AS MAP)); 19 | ``` 20 | 21 | This returns `["id", "name"]`. 22 | 23 | ## Notes 24 | 25 | A non-map value will cause an error. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/length.md: -------------------------------------------------------------------------------- 1 | # LENGTH 2 | 3 | `LENGTH` returns the number of elements in a list or map, or the number of characters in a string. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | LENGTH(value) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `value` – List, map or string expression. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT LENGTH('Hello.'); -- returns 6 19 | SELECT LENGTH(CAST('[1,2,3]' AS LIST)); -- returns 3 20 | SELECT LENGTH(CAST('{"a":1, "b":5}' AS MAP)); -- returns 2 21 | ``` 22 | 23 | ## Notes 24 | 25 | If `value` is `NULL` the result is `NULL`. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/skip.md: -------------------------------------------------------------------------------- 1 | # SKIP 2 | 3 | `SKIP` drops the first N elements from a list and returns the remaining values. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | SKIP(list, count) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `list` – List value. 14 | - `count` – Number of elements to drop. Must be a non‑negative integer. 15 | 16 | ## Examples 17 | 18 | ```sql 19 | SELECT SKIP(CAST('[1,2,3,4,5]' AS LIST), 2); 20 | ``` 21 | 22 | This returns `[3, 4, 5]`. 23 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/sort.md: -------------------------------------------------------------------------------- 1 | # SORT 2 | 3 | `SORT` orders the elements of a list. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | SORT(list [, 'ASC' | 'DESC']) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `list` – List to sort. 14 | - Optional sort order `'ASC'` (default) or `'DESC'`. 15 | 16 | ## Examples 17 | 18 | ```sql 19 | SELECT SORT(CAST('[3,1,4,2]' AS LIST)); -- [1,2,3,4] 20 | SELECT SORT(CAST('[3,1,4,2]' AS LIST), 'DESC'); -- [4,3,2,1] 21 | ``` 22 | 23 | ## Notes 24 | 25 | Non-comparable values or invalid order strings will produce an error. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/take.md: -------------------------------------------------------------------------------- 1 | # TAKE 2 | 3 | `TAKE` returns the first N elements from a list. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | TAKE(list, count) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `list` – List value. 14 | - `count` – Number of elements to take. Must be a non‑negative integer. 15 | 16 | ## Examples 17 | 18 | ```sql 19 | SELECT TAKE(CAST('[1,2,3,4,5]' AS LIST), 3); 20 | ``` 21 | 22 | This returns `[1, 2, 3]`. 23 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/list-map/values.md: -------------------------------------------------------------------------------- 1 | # VALUES 2 | 3 | `VALUES` returns the values of a map as a list. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | VALUES(map) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `map` – Map expression. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT VALUES(CAST('{"id":1, "name":"alice"}' AS MAP)); 19 | ``` 20 | 21 | This returns `[1, "alice"]`. 22 | 23 | ## Notes 24 | 25 | A non-map value will cause an error. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Math", 3 | "position": 2, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/div.md: -------------------------------------------------------------------------------- 1 | # DIV 2 | 3 | The `DIV` function is used to perform integer division. It takes two arguments (a dividend and a divisor) and returns the integer quotient of the division operation. Both dividend and divisor can be FLOAT or INTEGER type. The return type of the function is INTEGER. 4 | 5 | ## Example 6 | The following example demonstrates the usage of the `DIV` function in a SQL query: 7 | 8 | ```sql 9 | CREATE TABLE FloatDiv ( 10 | dividend FLOAT DEFAULT DIV(30, 11), 11 | divisor FLOAT DEFAULT DIV(3, 2) 12 | ); 13 | 14 | INSERT INTO FloatDiv (dividend, divisor) VALUES (12.0, 3.0), (12.34, 56.78), (-12.3, 4.0); 15 | 16 | SELECT DIV(dividend, divisor) FROM FloatDiv; 17 | ``` 18 | 19 | This will return the following result: 20 | 21 | ``` 22 | DIV(dividend, divisor) 23 | 4 24 | 0 25 | -4 26 | ``` 27 | 28 | ## Errors 29 | 1. If the divisor is zero, a `DivisorShouldNotBeZero` error will be raised. 30 | 2. If either of the arguments is not of FLOAT or INTEGER type, a `FunctionRequiresFloatOrIntegerValue` error will be raised. 31 | 3. If the number of arguments provided to the function is not equal to 2, a `FunctionArgsLengthNotMatching` error will be raised. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/exp.md: -------------------------------------------------------------------------------- 1 | # EXP 2 | 3 | The `EXP` function is used to calculate the exponential value of a number. It takes a single FLOAT or INTEGER argument and returns a FLOAT value representing the exponential value of the given number. 4 | 5 | ## Example 6 | The following example demonstrates the usage of the `EXP` function in a SQL query: 7 | 8 | ```sql 9 | SELECT 10 | EXP(2.0) as exp1, 11 | EXP(5.5) as exp2; 12 | ``` 13 | 14 | This will return the following result: 15 | 16 | ``` 17 | exp1 | exp2 18 | ---------------+------------------- 19 | 2.0_f64.exp() | 5.5_f64.exp() 20 | ``` 21 | 22 | ## Errors 23 | 1. If the argument is not of FLOAT or INTEGER type, a `FunctionRequiresFloatValue` error will be raised. 24 | 2. If the number of arguments provided to the function is not equal to 1, a `FunctionArgsLengthNotMatching` error will be raised. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/gcd.md: -------------------------------------------------------------------------------- 1 | # GCD 2 | 3 | The `GCD` function is used to find the greatest common divisor (GCD) of two integers. It takes two INTEGER arguments and returns an INTEGER value representing the greatest common divisor of the given integers. 4 | 5 | ## Example 6 | The following example demonstrates the usage of the `GCD` function in a SQL query: 7 | 8 | ```sql 9 | CREATE TABLE GcdI64 ( 10 | left INTEGER NULL DEFAULT GCD(3, 4), 11 | right INTEGER NULL 12 | ); 13 | 14 | INSERT INTO GcdI64 VALUES (0, 3), (2, 4), (6, 8), (3, 5), (1, NULL), (NULL, 1); 15 | 16 | SELECT GCD(left, right) AS test FROM GcdI64; 17 | ``` 18 | 19 | This will return the following result: 20 | 21 | ``` 22 | test 23 | 3 24 | 2 25 | 2 26 | 1 27 | NULL 28 | NULL 29 | ``` 30 | 31 | ## Errors 32 | 1. If either of the arguments is not of INTEGER type, a `FunctionRequiresIntegerValue` error will be raised. 33 | 2. If the number of arguments provided to the function is not equal to 2, a `FunctionArgsLengthNotMatching` error will be raised. 34 | 3. If either of the arguments is the minimum i64 value (`-9223372036854775808`), an overflow occurs when attempting to take the absolute value. In this case, a `GcdLcmOverflowError` is raised. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/ln.md: -------------------------------------------------------------------------------- 1 | # LN 2 | 3 | The `LN` function is used to calculate the natural logarithm (base `e`) of a number. It takes a single FLOAT or INTEGER argument and returns a FLOAT value representing the natural logarithm of the given number. 4 | 5 | ## Example 6 | The following example demonstrates the usage of the `LN` function in a SQL query: 7 | 8 | ```sql 9 | CREATE TABLE SingleItem (id INTEGER DEFAULT LN(10)); 10 | 11 | INSERT INTO SingleItem VALUES (0); 12 | 13 | SELECT 14 | LN(64.0) as ln1, 15 | LN(0.04) as ln2 16 | FROM SingleItem; 17 | ``` 18 | 19 | This will return the following result: 20 | 21 | ``` 22 | ln1 | ln2 23 | --------+------------------- 24 | 4.1589 | -3.2189 25 | ``` 26 | 27 | ## Errors 28 | 1. If the argument is not of FLOAT or INTEGER type, a `FunctionRequiresFloatValue` error will be raised. 29 | 2. If the number of arguments provided to the function is not equal to 1, a `FunctionArgsLengthNotMatching` error will be raised. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/log.md: -------------------------------------------------------------------------------- 1 | # LOG 2 | 3 | The `LOG` function calculates the logarithm of a number with a specified base. It takes two FLOAT or INTEGER arguments and returns a FLOAT value representing the logarithm of the first argument with the base specified by the second argument. 4 | 5 | ## Example 6 | The following example demonstrates the usage of the `LOG` function in a SQL query: 7 | 8 | ```sql 9 | CREATE TABLE SingleItem (id INTEGER DEFAULT LOG(2, 64)); 10 | 11 | INSERT INTO SingleItem VALUES (0); 12 | 13 | SELECT 14 | LOG(64.0, 2.0) as log_1, 15 | LOG(0.04, 10.0) as log_2 16 | FROM SingleItem; 17 | ``` 18 | 19 | This will return the following result: 20 | 21 | ``` 22 | log_1 | log_2 23 | ------+------------------- 24 | 6.0 | -1.39794 25 | ``` 26 | 27 | ## Errors 28 | 1. If either of the arguments is not of FLOAT or INTEGER type, a `FunctionRequiresFloatValue` error will be raised. 29 | 2. If the number of arguments provided to the function is not equal to 2, a `FunctionArgsLengthNotMatching` error will be raised. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/log10.md: -------------------------------------------------------------------------------- 1 | # LOG10 2 | 3 | The `LOG10` function is used to calculate the base-10 logarithm of a number. It takes a single FLOAT or INTEGER argument and returns a FLOAT value representing the base-10 logarithm of the given number. 4 | 5 | ## Example 6 | The following example demonstrates the usage of the `LOG10` function in a SQL query: 7 | 8 | ```sql 9 | CREATE TABLE SingleItem (id INTEGER DEFAULT LOG10(100)); 10 | 11 | INSERT INTO SingleItem VALUES (0); 12 | 13 | SELECT 14 | LOG10(64.0) as log10_1, 15 | LOG10(0.04) as log10_2 16 | FROM SingleItem; 17 | ``` 18 | 19 | This will return the following result: 20 | 21 | ``` 22 | log10_1 | log10_2 23 | --------+------------------- 24 | 1.8062 | -1.3979 25 | ``` 26 | 27 | ## Errors 28 | 1. If the argument is not of FLOAT or INTEGER type, a `FunctionRequiresFloatValue` error will be raised. 29 | 2. If the number of arguments provided to the function is not equal to 1, a `FunctionArgsLengthNotMatching` error will be raised. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/log2.md: -------------------------------------------------------------------------------- 1 | # LOG2 2 | 3 | The `LOG2` function is used to calculate the base-2 logarithm of a number. It takes a single FLOAT or INTEGER argument and returns a FLOAT value representing the base-2 logarithm of the given number. 4 | 5 | ## Example 6 | The following example demonstrates the usage of the `LOG2` function in a SQL query: 7 | 8 | ```sql 9 | CREATE TABLE SingleItem (id INTEGER DEFAULT LOG2(1024)); 10 | 11 | INSERT INTO SingleItem VALUES (0); 12 | 13 | SELECT 14 | LOG2(64.0) as log2_1, 15 | LOG2(0.04) as log2_2 16 | FROM SingleItem; 17 | ``` 18 | 19 | This will return the following result: 20 | 21 | ``` 22 | log2_1 | log2_2 23 | -------+------------------- 24 | 6.0 | -4.5850 25 | ``` 26 | 27 | ## Errors 28 | 1. If the argument is not of FLOAT or INTEGER type, a `FunctionRequiresFloatValue` error will be raised. 29 | 2. If the number of arguments provided to the function is not equal to 1, a `FunctionArgsLengthNotMatching` error will be raised. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/mod.md: -------------------------------------------------------------------------------- 1 | # MOD 2 | 3 | The `MOD` function is used to calculate the remainder of a division operation. It takes two arguments (a dividend and a divisor) and returns the remainder of the division operation. Both dividend and divisor can be FLOAT or INTEGER type. The return type of the function is FLOAT. 4 | 5 | ## Example 6 | The following example demonstrates the usage of the `MOD` function in a SQL query: 7 | 8 | ```sql 9 | CREATE TABLE FloatDiv ( 10 | dividend FLOAT DEFAULT MOD(30, 11), 11 | divisor FLOAT DEFAULT DIV(3, 2) 12 | ); 13 | 14 | INSERT INTO FloatDiv (dividend, divisor) VALUES (12.0, 3.0), (12.34, 56.78), (-12.3, 4.0); 15 | 16 | SELECT MOD(dividend, divisor) FROM FloatDiv; 17 | ``` 18 | 19 | This will return the following result: 20 | 21 | ``` 22 | MOD(dividend, divisor) 23 | 0.0 24 | 12.34 25 | -0.3 26 | ``` 27 | 28 | ## Errors 29 | 1. If the divisor is zero, a `DivisorShouldNotBeZero` error will be raised. 30 | 2. If either of the arguments is not of FLOAT or INTEGER type, a `FunctionRequiresFloatOrIntegerValue` error will be raised. 31 | 3. If the number of arguments provided to the function is not equal to 2, a `FunctionArgsLengthNotMatching` error will be raised. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/pi.md: -------------------------------------------------------------------------------- 1 | # PI 2 | 3 | The `PI` function is used to retrieve the mathematical constant π (pi), which is approximately 3.141592653589793. The function takes no arguments. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | PI() 9 | ``` 10 | 11 | ## Examples 12 | 13 | Let's consider a table named `SingleItem` with the following schema: 14 | 15 | ```sql 16 | CREATE TABLE SingleItem (id FLOAT); 17 | ``` 18 | 19 | Insert a row into the `SingleItem` table: 20 | 21 | ```sql 22 | INSERT INTO SingleItem VALUES (0); 23 | ``` 24 | 25 | ### Example 1: Using PI function 26 | 27 | ```sql 28 | SELECT PI() as pi FROM SingleItem; 29 | ``` 30 | 31 | Result: 32 | 33 | ``` 34 | pi 35 | ---------------- 36 | 3.141592653589793 37 | ``` 38 | 39 | ## Errors 40 | 41 | The `PI` function expects no arguments. Providing any arguments will result in an error. 42 | 43 | ### Example 2: Using PI with an argument 44 | 45 | ```sql 46 | SELECT PI(0) as pi FROM SingleItem; 47 | ``` 48 | 49 | Error: Function expects 0 arguments, but 1 was provided. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/math/sqrt.md: -------------------------------------------------------------------------------- 1 | # SQRT 2 | 3 | The `SQRT` function is used to calculate the square root of a number. It takes one argument, which must be of the FLOAT type. The result will also be of the FLOAT type. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | SQRT(number) 9 | ``` 10 | 11 | ## Examples 12 | 13 | 1. Using the `SQRT` function: 14 | 15 | ```sql 16 | SELECT SQRT(2.0) as sqrt_1; 17 | -- Result: 1.4142135623730951 18 | ``` 19 | 20 | 2. Using the `SQRT` function with a decimal: 21 | 22 | ```sql 23 | SELECT SQRT(0.07) as sqrt_2; 24 | -- Result: 0.2645751311064591 25 | ``` 26 | 27 | 3. Using the `SQRT` function with an integer: 28 | 29 | ```sql 30 | SELECT SQRT(32) as sqrt_with_int; 31 | -- Result: 5.656854249492381 32 | ``` 33 | 34 | 4. Using the `SQRT` function with zero: 35 | 36 | ```sql 37 | SELECT SQRT(0) as sqrt_with_zero; 38 | -- Result: 0.0 39 | ``` 40 | 41 | 5. Using the `SQRT` function with NULL: 42 | 43 | ```sql 44 | SELECT SQRT(NULL) AS sqrt; 45 | -- Result: NULL 46 | ``` 47 | 48 | ## Error Cases 49 | 50 | 1. The `SQRT` function requires the argument to be of FLOAT type: 51 | 52 | ```sql 53 | SELECT SQRT('string') AS sqrt; 54 | -- Error: SqrtOnNonNumeric("string") 55 | ``` -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/others/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Others", 3 | "position": 6, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/others/coalesce.md: -------------------------------------------------------------------------------- 1 | # COALESCE 2 | 3 | The `COALESCE` function returns the first non-`NULL` value from the list of expressions. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | COALESCE(expr1, expr2, ...) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `expr1`, `expr2`, ... – Expressions evaluated in order. At least one expression must be provided. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | CREATE TABLE example (a INT NULL, b INT NULL); 19 | INSERT INTO example VALUES (NULL, 2), (3, NULL), (NULL, NULL); 20 | 21 | SELECT COALESCE(a, b, 0) AS result FROM example; 22 | ``` 23 | 24 | This returns `2`, `3` and `0` for the three rows. 25 | 26 | ## Notes 27 | 28 | If all arguments are `NULL`, the result is `NULL`. 29 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/others/greatest.md: -------------------------------------------------------------------------------- 1 | # GREATEST 2 | 3 | `GREATEST` returns the highest value among the supplied expressions. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | GREATEST(expr1, expr2, ...) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `expr1`, `expr2`, ... – Two or more comparable expressions. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT GREATEST(1, 6, 9, 7, 0, 10) AS result; 19 | ``` 20 | 21 | The query above returns `10`. 22 | 23 | ## Notes 24 | 25 | All arguments must be of comparable types. At least two expressions are required. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/text/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Text", 3 | "position": 1, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/text/lower.md: -------------------------------------------------------------------------------- 1 | # LOWER 2 | 3 | The `LOWER` function in SQL returns a string in which all alphabetic characters in a specified string are converted to lowercase. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | LOWER(string) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `string`: The original string to convert. 14 | 15 | ## Return Value 16 | 17 | The function returns a new string that is the same as the original string, but with all uppercase characters converted to lowercase. Non-alphabetic characters in the string are unaffected. 18 | 19 | ## Errors 20 | 21 | - If the `string` argument is not a string, a `FunctionRequiresStringValue` error will be returned. 22 | 23 | ## Examples 24 | 25 | Consider a table `Item` created and filled with the following data: 26 | 27 | ```sql 28 | CREATE TABLE Item ( 29 | name TEXT 30 | ); 31 | INSERT INTO Item VALUES ('ABCD'), ('Abcd'), ('abcd'); 32 | ``` 33 | 34 | You can use the `LOWER` function to convert all `name` values to lowercase: 35 | 36 | ```sql 37 | SELECT LOWER(name) AS lower_name FROM Item; 38 | ``` 39 | 40 | This will return: 41 | 42 | ``` 43 | abcd 44 | abcd 45 | abcd 46 | ``` 47 | 48 | Note that the `LOWER` function affects only alphabetic characters. Non-alphabetic characters in the string remain unchanged. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/text/md5.md: -------------------------------------------------------------------------------- 1 | # MD5 2 | 3 | `MD5` calculates the MD5 hash of a string. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | MD5(text) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `text` – The string to hash. 14 | 15 | ## Examples 16 | 17 | ```sql 18 | SELECT MD5('GlueSQL'); 19 | ``` 20 | 21 | This returns `4274ecec96f3ee59b51b168dc6137231`. 22 | 23 | ## Notes 24 | 25 | `MD5` requires exactly one argument. 26 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/text/repeat.md: -------------------------------------------------------------------------------- 1 | # REPEAT 2 | 3 | The `REPEAT` function in SQL is used to repeat a string for a specified number of times. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | REPEAT(string, number) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `string`: The string to be repeated. 14 | - `number`: The number of times to repeat the string. 15 | 16 | ## Return Value 17 | 18 | The function returns a string which is the concatenation of the input string repeated the specified number of times. 19 | 20 | ## Errors 21 | 22 | - If the parameters are not in the correct format, a `TranslateError::FunctionArgsLengthNotMatching` error will be returned. This function requires exactly two arguments. 23 | - If either `string` or `number` are not string values, a `EvaluateError::FunctionRequiresStringValue` error will be returned. 24 | 25 | ## Examples 26 | 27 | Consider a table `Item` created and filled with the following data: 28 | 29 | ```sql 30 | CREATE TABLE Item (name TEXT); 31 | INSERT INTO Item VALUES ('hello'); 32 | ``` 33 | 34 | You can use the `REPEAT` function to repeat the `name` values: 35 | 36 | ```sql 37 | SELECT REPEAT(name, 2) AS test FROM Item; 38 | ``` 39 | 40 | This will return: 41 | 42 | ``` 43 | hellohello 44 | ``` 45 | 46 | The 'hello' string is repeated twice as specified by the second parameter to the `REPEAT` function. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/text/reverse.md: -------------------------------------------------------------------------------- 1 | # REVERSE 2 | 3 | The `REVERSE` function in SQL is used to reverse a string. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | REVERSE(string) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `string`: The string to be reversed. 14 | 15 | ## Return Value 16 | 17 | The function returns a string which is the reverse of the input string. 18 | 19 | ## Errors 20 | 21 | If the parameter is not a string value, a `EvaluateError::FunctionRequiresStringValue` error will be returned. 22 | 23 | ## Examples 24 | 25 | Consider a table `Item` created and filled with the following data: 26 | 27 | ```sql 28 | CREATE TABLE Item (name TEXT); 29 | INSERT INTO Item VALUES ('Let''s meet'); 30 | ``` 31 | 32 | You can use the `REVERSE` function to reverse the `name` values: 33 | 34 | ```sql 35 | SELECT REVERSE(name) AS test FROM Item; 36 | ``` 37 | 38 | This will return: 39 | 40 | ``` 41 | teem s'teL 42 | ``` 43 | 44 | The 'Let''s meet' string is reversed as 'teem s'teL'. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/functions/text/upper.md: -------------------------------------------------------------------------------- 1 | # UPPER 2 | 3 | The `UPPER` function in SQL converts all lowercase alphabetic characters in a specified string to uppercase. 4 | 5 | ## Syntax 6 | 7 | ```sql 8 | UPPER(string) 9 | ``` 10 | 11 | ## Parameters 12 | 13 | - `string`: The original string to convert. 14 | 15 | ## Return Value 16 | 17 | The function returns a new string that is the same as the original string, but with all lowercase characters converted to uppercase. Non-alphabetic characters in the string are unaffected. 18 | 19 | ## Errors 20 | 21 | - If the `string` argument is not a string, a `FunctionRequiresStringValue` error will be returned. 22 | 23 | ## Examples 24 | 25 | Consider a table `Item` created and filled with the following data: 26 | 27 | ```sql 28 | CREATE TABLE Item ( 29 | name TEXT 30 | ); 31 | INSERT INTO Item VALUES ('abcd'), ('Abcd'), ('ABCD'); 32 | ``` 33 | 34 | You can use the `UPPER` function to convert all `name` values to uppercase: 35 | 36 | ```sql 37 | SELECT UPPER(name) AS upper_name FROM Item; 38 | ``` 39 | 40 | This will return: 41 | 42 | ``` 43 | ABCD 44 | ABCD 45 | ABCD 46 | ``` 47 | 48 | Note that the `UPPER` function affects only alphabetic characters. Non-alphabetic characters in the string remain unchanged. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/statements/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Statements", 3 | "position": 1, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/statements/data-definition/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Data definition", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/statements/data-manipulation/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Data manipulation", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/statements/metadata/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Metadata", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/sql-syntax/statements/metadata/show-tables.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # SHOW TABLES 6 | 7 | The `SHOW TABLES` statement in GlueSQL is used to display a list of tables available in the database. This statement is useful when you want to inspect the current structure of your database or when you want to manage multiple tables. 8 | 9 | ## Syntax 10 | 11 | ```sql 12 | SHOW TABLES; 13 | ``` 14 | 15 | ## Example 16 | 17 | Consider the following example where we create a few tables and then use the `SHOW TABLES` statement to list them: 18 | 19 | ```sql 20 | CREATE TABLE Foo (id INTEGER, name TEXT NULL, type TEXT NULL); 21 | CREATE TABLE Zoo (id INTEGER); 22 | CREATE TABLE Bar (id INTEGER, name TEXT NULL); 23 | 24 | SHOW TABLES; 25 | ``` 26 | 27 | The output of the `SHOW TABLES` statement will be: 28 | 29 | ``` 30 | Bar 31 | Foo 32 | Zoo 33 | ``` 34 | 35 | The tables are listed in alphabetical order. -------------------------------------------------------------------------------- /docs/docs/sql-syntax/statements/querying/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Querying", 3 | "position": 1 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/storages/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Storages", 3 | "position": 4, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/storages/developing-custom-storages/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Developing Custom Storages", 3 | "position": 3, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/storages/developing-custom-storages/store-traits/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Understanding Store traits", 3 | "position": 2, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/storages/developing-custom-storages/store-traits/custom-function-mut.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # CustomFunctionMut 6 | 7 | By implementing both the `CustomFunction` and `CustomFunctionMut` traits, users can create, use, and delete user-level custom functions. Although GlueSQL plans to continuously add various functions, users may still find them insufficient. In such cases, users can create their own user-level custom functions to supplement the built-in functions. Additionally, if there are repetitive business logic codes, they can be stored as custom functions. 8 | 9 | Example: 10 | 11 | ```sql 12 | CREATE FUNCTION ADD_ONE (n INT, x INT DEFAULT 1) RETURN n + x; 13 | 14 | SELECT ADD_ONE(10) AS test; 15 | 16 | DROP FUNCTION ADD_ONE; 17 | ``` 18 | 19 | There are two methods available: 20 | 21 | 1. `insert_function`: This method inserts a new custom function into the storage system. 22 | 23 | 2. `delete_function`: This method deletes a custom function from the storage system using the provided function name. 24 | 25 | ```rust 26 | #[async_trait(?Send)] 27 | pub trait CustomFunctionMut { 28 | async fn insert_function(&mut self, _func: StructCustomFunction) -> Result<()>; 29 | 30 | async fn delete_function(&mut self, _func_name: &str) -> Result<()>; 31 | } 32 | ``` -------------------------------------------------------------------------------- /docs/docs/storages/developing-custom-storages/store-traits/custom-function.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # CustomFunction 6 | 7 | The `CustomFunction` trait is an optional trait for supporting user-level custom functions. Through the `CustomFunction` trait, you can retrieve custom functions stored in the storage system. You can choose to implement the `CustomFunction` trait alone or together with the `CustomFunctionMut` trait. 8 | 9 | In some cases, you might want to provide storage-specific functions pre-built and separately available for each storage system. In such cases, you can implement the `CustomFunction` trait and create additional functions stored in advance when using it. To achieve this, the `CustomFunction` and `CustomFunctionMut` traits are provided separately for implementation. 10 | 11 | There are two methods available: 12 | 13 | 1. `fetch_function`: This method retrieves a custom function from the storage system using the provided function name. 14 | 15 | 2. `fetch_all_functions`: This method retrieves all custom functions stored in the storage system. 16 | 17 | ```rust 18 | #[async_trait(?Send)] 19 | pub trait CustomFunction { 20 | async fn fetch_function(&self, _func_name: &str) -> Result<Option<&StructCustomFunction>>; 21 | 22 | async fn fetch_all_functions(&self) -> Result<Vec<&StructCustomFunction>>; 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/docs/storages/supported-storages/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Supported Storages", 3 | "position": 2, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/storages/supported-storages/csv-storage.md: -------------------------------------------------------------------------------- 1 | # CSV Storage 2 | 3 | Introducing `CSVStorage`: a utility to process *.csv files, enabling SQL-like query operations such as SELECT, INSERT, and UPDATE. 4 | 5 | ## Key Features: 6 | 7 | 1. **SQL Queries on CSV**: Directly parse and operate on *.csv files using familiar SQL query operations. 8 | 9 | 2. **Optional Schema Support**: An associated schema can be provided for each CSV file. For instance, for a data file named `Book.csv`, its corresponding schema file should be named `Book.sql`. 10 | - If an associated schema file is found, it will be read and applied. 11 | - In the absence of a schema file, the first row of the data file will be treated as column headers and all column types will default to TEXT. 12 | 13 | 3. **Type Info File for Schemaless Data**: An auxiliary types file (`*.types.csv`) can be used to support data type recognition for schemaless data. 14 | - For a CSV data file named `Book.csv`, its corresponding types file will be `Book.types.csv`. 15 | - The types file will have a 1:1 mapping with the CSV data file entries, specifying the data type for each entry in alignment with the GlueSQL conventions. 16 | -------------------------------------------------------------------------------- /docs/docs/storages/supported-storages/redb-storage.md: -------------------------------------------------------------------------------- 1 | # Redb Storage 2 | 3 | RedbStorage allows GlueSQL to persist data using the [redb](https://github.com/cberner/redb) embedded key-value database. It provides ACID transactions, fast single-file access, and a stable API. 4 | 5 | RedbStorage implements GlueSQL's `Store`, `StoreMut`, and `Transaction` traits. 6 | ## Example 7 | 8 | ```rust 9 | use gluesql::{prelude::Glue, redb_storage::RedbStorage}; 10 | 11 | #[tokio::main] 12 | async fn main() { 13 | let storage = RedbStorage::new("data/my_db.redb").unwrap(); 14 | let mut glue = Glue::new(storage); 15 | 16 | let sql = " 17 | CREATE TABLE Foo (id INT, name TEXT); 18 | INSERT INTO Foo VALUES (1, 'Alice'), (2, 'Bob'); 19 | SELECT * FROM Foo; 20 | "; 21 | 22 | let payloads = glue.execute(sql).await.unwrap(); 23 | println!("{:#?}", payloads); 24 | } 25 | ``` 26 | 27 | ## Things to keep in mind 28 | 29 | - Nested transactions are not supported. 30 | - Only one RedbStorage instance should open the same database file at a time. 31 | 32 | RedbStorage gives you an embedded, serverless database that integrates seamlessly with GlueSQL. Use `RedbStorage::new` to open or create a database file and execute SQL through `Glue`. 33 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "start:blog": "GLUESQL_DOC_TYPE=blog docusaurus start", 9 | "build": "docusaurus build", 10 | "build:blog": "GLUESQL_DOC_TYPE=blog docusaurus build", 11 | "swizzle": "docusaurus swizzle", 12 | "deploy": "docusaurus deploy", 13 | "clear": "docusaurus clear", 14 | "serve": "docusaurus serve", 15 | "write-translations": "docusaurus write-translations", 16 | "write-heading-ids": "docusaurus write-heading-ids" 17 | }, 18 | "dependencies": { 19 | "@docusaurus/core": "^2.4.3", 20 | "@docusaurus/preset-classic": "^2.4.3", 21 | "@mdx-js/react": "^1.6.22", 22 | "clsx": "^1.2.1", 23 | "prism-react-renderer": "^1.3.5", 24 | "react": "^17.0.2", 25 | "react-dom": "^17.0.2" 26 | }, 27 | "devDependencies": { 28 | "@docusaurus/module-type-aliases": "^2.4.3" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | }, 42 | "engines": { 43 | "node": ">=16.14" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | autoSidebar: [{type: 'autogenerated', dirName: '.'}], 17 | }; 18 | 19 | module.exports = sidebars; 20 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/gluesql.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/docs/static/img/gluesql.jpg -------------------------------------------------------------------------------- /pkg/javascript/.gitignore: -------------------------------------------------------------------------------- 1 | dist_web/ 2 | dist_nodejs/ 3 | dist/ 4 | node_modules/ 5 | bin/ 6 | pkg/ 7 | target/ 8 | **/*.rs.bk 9 | wasm-pack.log 10 | Cargo.lock 11 | package-lock.json 12 | -------------------------------------------------------------------------------- /pkg/javascript/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-js" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | readme = false 11 | 12 | [lib] 13 | crate-type = ["cdylib", "rlib"] 14 | 15 | [features] 16 | default = ["console_error_panic_hook"] 17 | 18 | nodejs = ["console_error_panic_hook"] 19 | 20 | [dependencies] 21 | gluesql-core.workspace = true 22 | gluesql_memory_storage.workspace = true 23 | gluesql-web-storage.workspace = true 24 | gluesql-idb-storage.workspace = true 25 | gluesql-composite-storage.workspace = true 26 | 27 | wasm-bindgen = { version = "0.2.100" } 28 | wasm-bindgen-futures = "0.4.29" 29 | js-sys = "0.3" 30 | 31 | serde = "1" 32 | serde_json = "1" 33 | gloo-utils = { version = "0.1.6", features = ["serde"] } 34 | 35 | # The `console_error_panic_hook` crate provides better debugging of panics by 36 | # logging them with `console.error`. This is great for development, but requires 37 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 38 | # code size when deploying. 39 | console_error_panic_hook = { version = "0.1.6", optional = true } 40 | 41 | [dev-dependencies] 42 | test-suite.workspace = true 43 | 44 | wasm-bindgen-test = "0.3.50" 45 | async-trait = "0.1" 46 | -------------------------------------------------------------------------------- /pkg/javascript/DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | ## 🚴 Usage 2 | 3 | ### Setup 4 | Enable `opt-level = "s"` option 5 | ``` 6 | # ref. https://github.com/rustwasm/wasm-pack/issues/1111 7 | # enable this only for gluesql-js build 8 | [profile.release] 9 | opt-level = "s" 10 | ``` 11 | 12 | ### Build 13 | ``` 14 | # browser module, webpack and rollup 15 | wasm-pack build --no-pack --target web --no-typescript --release --out-dir ./dist_web 16 | 17 | # nodejs 18 | wasm-pack build --no-pack --target nodejs --no-typescript --release --out-dir ./dist_nodejs -- --no-default-features --features nodejs 19 | ``` 20 | 21 | ### 🔬 Test in Headless Browsers with `wasm-pack test` 22 | ``` 23 | wasm-pack test --headless --firefox --chrome 24 | ``` 25 | -------------------------------------------------------------------------------- /pkg/javascript/examples/nodejs/main.js: -------------------------------------------------------------------------------- 1 | const { gluesql } = require('../../gluesql.node.js'); 2 | const db = gluesql(); 3 | 4 | async function run() { 5 | await db.query(` 6 | CREATE TABLE User (id INTEGER, name TEXT); 7 | CREATE TABLE Device (name TEXT, userId INTEGER); 8 | INSERT INTO User VALUES 9 | (1, 'glue'), (2, 'sticky'), (3, 'watt'); 10 | INSERT INTO Device VALUES 11 | ('Phone', 1), ('Mic', 1), ('Monitor', 3), 12 | ('Mouse', 2), ('Touchpad', 2); 13 | `); 14 | 15 | let sql; 16 | 17 | sql = 'SHOW TABLES;'; 18 | const [{ tables }] = await db.query(sql); 19 | console.log(`\n[Query]\n${sql}`); 20 | console.table(tables); 21 | 22 | sql = ` 23 | SELECT 24 | u.name as user, 25 | d.name as device 26 | FROM User u 27 | JOIN Device d ON u.id = d.userId 28 | `.trim().replace(/[ ]{4}/g, ''); 29 | const [{ rows }] = await db.query(sql); 30 | console.log(`\n[Query]\n${sql}`); 31 | console.table(rows); 32 | } 33 | 34 | run(); 35 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/module/README.md: -------------------------------------------------------------------------------- 1 | ## Guide: Running module example 2 | 3 | ### How to run? 4 | 0. Before going on, you should build and generate `dist` directory. So go to under `gluesql/gluesql-js/web`. And run these commands 5 | ```sh 6 | # install dependencies 7 | $ yarn 8 | 9 | # build for examples/web/module 10 | $ yarn build:browser 11 | ``` 12 | 13 | 1. Go to under `gluesql/gluesql-js` 14 | 15 | 2. Serve files using proper application 16 | There are many http server applications. Here are some examples 17 | 18 | - [simple-http-server](https://crates.io/crates/simple-http-server) 19 | ```sh 20 | # 1. install 21 | $ cargo install simple-http-server 22 | 23 | # 2. serve files under `gluesql/gluesql-js`. Now open the browser and go to `http://localhost:3030` 24 | $ simple-http-server --port 3000 25 | 26 | # 3. navigate to `examples/web/module/index.html`. The url should be `http://localhost:3000/examples/web/module/index.html` 27 | # 4. Check the result 28 | ``` 29 | 30 | - [http-server](https://www.npmjs.com/package/http-server) 31 | ```sh 32 | # 1. install or run 33 | $ npx http-server 34 | 35 | # Remaining steps are same as `simple-http-server` section guide 36 | ``` 37 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/rollup/index.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html> 3 | <head> 4 | <meta charset="utf-8"> 5 | <title>GlueSQL Javascript Rollup example</title> 6 | <script src="dist/bundle.js"></script> 7 | </head> 8 | <style> 9 | body { 10 | padding: 0; 11 | margin: 0; 12 | } 13 | 14 | #box { 15 | display: flex; 16 | flex-direction: column; 17 | padding: 90px 20px 80px 20px; 18 | background-color: #222; 19 | 20 | color: white; 21 | } 22 | 23 | code { 24 | margin: 5px; 25 | padding: 10px; 26 | font-family: monospace; 27 | border: 1px solid white; 28 | } 29 | 30 | </style> 31 | <body> 32 | <div id="box"> 33 | </div> 34 | </body> 35 | </html> 36 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/rollup/main.js: -------------------------------------------------------------------------------- 1 | import { gluesql } from 'gluesql/gluesql.rollup'; 2 | 3 | async function run() { 4 | const db = await gluesql(); 5 | await db.loadIndexedDB(); 6 | 7 | const result = await db.query(` 8 | DROP TABLE IF EXISTS Foo, Bar; 9 | CREATE TABLE Foo (id INTEGER, name TEXT); 10 | CREATE TABLE Bar (bar_id INTEGER) ENGINE = indexedDB; 11 | INSERT INTO Foo VALUES (1, 'hello'), (2, 'world'); 12 | INSERT INTO Bar VALUES (10), (20); 13 | SELECT *, id as wow_id FROM Foo JOIN Bar; 14 | `); 15 | 16 | for (const item of result) { 17 | const node = document.createElement('code'); 18 | 19 | node.innerHTML = ` 20 | type: ${item.type} 21 | <br> 22 | ${item.affected ? `affected: ${item.affected}` : ''} 23 | ${item.rows ? `rows: ${JSON.stringify(item.rows)}` : ''} 24 | `; 25 | 26 | console.log(item); 27 | document.querySelector('#box').append(node); 28 | } 29 | } 30 | 31 | window.onload = run; 32 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/rollup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gluesql-js-web-rollup-example", 3 | "version": "0.1.0", 4 | "description": "GlueSQL javascript rollup example", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/gluesql/gluesql.git" 9 | }, 10 | "author": "Taehoon Moon", 11 | "license": "Apache-2.0", 12 | "bugs": { 13 | "url": "https://github.com/gluesql/gluesql/issues" 14 | }, 15 | "homepage": "https://github.com/gluesql/gluesql#readme", 16 | "scripts": { 17 | "build": "rollup -c" 18 | }, 19 | "devDependencies": { 20 | "@rollup/plugin-node-resolve": "^13.2.1", 21 | "@rollup/plugin-wasm": "^6.1.3", 22 | "gluesql": "file:../../..", 23 | "rollup": "^2.70.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/rollup/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import { wasm } from '@rollup/plugin-wasm'; 3 | 4 | export default { 5 | input: 'main.js', 6 | output: { 7 | file: 'dist/bundle.js', 8 | format: 'iife', 9 | }, 10 | plugins: [ 11 | resolve({ browser: true }), 12 | wasm({ targetEnv: 'auto-inline' }), 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/webpack/index.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html> 3 | <head> 4 | <meta charset="utf-8"> 5 | <title>GlueSQL Javascript Webpack example</title> 6 | <script src="dist/bundle.js"></script> 7 | </head> 8 | <style> 9 | body { 10 | padding: 0; 11 | margin: 0; 12 | } 13 | 14 | #box { 15 | display: flex; 16 | flex-direction: column; 17 | padding: 90px 20px 80px 20px; 18 | background-color: #222; 19 | 20 | color: white; 21 | } 22 | 23 | code { 24 | margin: 5px; 25 | padding: 10px; 26 | font-family: monospace; 27 | border: 1px solid white; 28 | } 29 | 30 | </style> 31 | <body> 32 | <div id="box"> 33 | </div> 34 | </body> 35 | </html> 36 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/webpack/main.js: -------------------------------------------------------------------------------- 1 | import { gluesql } from 'gluesql'; 2 | 3 | async function run() { 4 | const db = await gluesql(); 5 | await db.loadIndexedDB(); 6 | 7 | const result = await db.query(` 8 | DROP TABLE IF EXISTS Foo; 9 | CREATE TABLE Foo (id INTEGER, name TEXT) ENGINE = indexedDB; 10 | INSERT INTO Foo VALUES (1, 'hello'), (2, 'world'); 11 | SELECT *, id as wow_id FROM Foo; 12 | `); 13 | 14 | for (const item of result) { 15 | const node = document.createElement('code'); 16 | 17 | node.innerHTML = ` 18 | type: ${item.type} 19 | <br> 20 | ${item.affected ? `affected: ${item.affected}` : ''} 21 | ${item.rows ? `rows: ${JSON.stringify(item.rows)}` : ''} 22 | `; 23 | 24 | console.log(item); 25 | document.querySelector('#box').append(node); 26 | } 27 | } 28 | 29 | window.onload = run; 30 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gluesql-js-web-webpack-example", 3 | "version": "0.1.0", 4 | "description": "GlueSQL javascript webpack example", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/gluesql/gluesql.git" 9 | }, 10 | "author": "Taehoon Moon", 11 | "license": "Apache-2.0", 12 | "bugs": { 13 | "url": "https://github.com/gluesql/gluesql/issues" 14 | }, 15 | "homepage": "https://github.com/gluesql/gluesql#readme", 16 | "scripts": { 17 | "build": "yarn upgrade gluesql && webpack" 18 | }, 19 | "devDependencies": { 20 | "gluesql": "file:../../..", 21 | "webpack": "^5.72.0", 22 | "webpack-cli": "^4.9.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/javascript/examples/web/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: "./main.js", 5 | output: { 6 | path: path.resolve(__dirname, "dist"), 7 | filename: "bundle.js", 8 | }, 9 | mode: "development" 10 | }; 11 | -------------------------------------------------------------------------------- /pkg/javascript/gluesql.js: -------------------------------------------------------------------------------- 1 | import init, { Glue } from './dist_web/gluesql_js.js'; 2 | 3 | let loaded = false; 4 | 5 | async function load(module_or_path) { 6 | await init(module_or_path); 7 | 8 | loaded = true; 9 | } 10 | 11 | export async function gluesql(module_or_path) { 12 | if (!loaded) { 13 | await load(module_or_path); 14 | } 15 | 16 | return new Glue(); 17 | } 18 | -------------------------------------------------------------------------------- /pkg/javascript/gluesql.node.js: -------------------------------------------------------------------------------- 1 | const { Glue } = require('./dist_nodejs/gluesql_js.js'); 2 | 3 | function gluesql() { 4 | return new Glue(); 5 | } 6 | 7 | module.exports = { gluesql }; 8 | -------------------------------------------------------------------------------- /pkg/javascript/gluesql.rollup.js: -------------------------------------------------------------------------------- 1 | import init, { Glue } from './dist_web/gluesql_js.js'; 2 | import loadDB from './dist_web/gluesql_js_bg.wasm'; 3 | 4 | let loaded = false; 5 | 6 | export async function gluesql() { 7 | if (!loaded) { 8 | const instance = await loadDB(); 9 | await init(instance); 10 | 11 | loaded = true; 12 | } 13 | 14 | return new Glue(); 15 | } 16 | -------------------------------------------------------------------------------- /pkg/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gluesql", 3 | "version": "0.17.0", 4 | "description": "GlueSQL is quite sticky, it attaches to anywhere", 5 | "browser": "gluesql.js", 6 | "main": "gluesql.node.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/gluesql/gluesql.git" 10 | }, 11 | "keywords": [ 12 | "SQL", 13 | "Database", 14 | "SQL.js", 15 | "websql", 16 | "rust", 17 | "webassembly", 18 | "wasm" 19 | ], 20 | "author": "Taehoon Moon", 21 | "license": "Apache-2.0", 22 | "bugs": { 23 | "url": "https://github.com/gluesql/gluesql/issues" 24 | }, 25 | "homepage": "https://gluesql.org/docs", 26 | "files": [ 27 | "dist_nodejs/gluesql_js.js", 28 | "dist_nodejs/gluesql_js_bg.wasm", 29 | "dist_web/gluesql_js.js", 30 | "dist_web/gluesql_js_bg.wasm", 31 | "gluesql.js", 32 | "gluesql.node.js", 33 | "gluesql.rollup.js", 34 | "package.json", 35 | "README.md" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /pkg/javascript/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn set_panic_hook() { 2 | // When the `console_error_panic_hook` feature is enabled, we can call the 3 | // `set_panic_hook` function at least once during initialization, and then 4 | // we will get better error messages if our code ever panics. 5 | // 6 | // For more details see 7 | // https://github.com/rustwasm/console_error_panic_hook#readme 8 | #[cfg(feature = "console_error_panic_hook")] 9 | console_error_panic_hook::set_once(); 10 | } 11 | -------------------------------------------------------------------------------- /pkg/javascript/tests/composite_storage.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "wasm32")] 2 | 3 | use { 4 | async_trait::async_trait, gluesql_composite_storage::CompositeStorage, 5 | gluesql_core::prelude::Glue, gluesql_memory_storage::MemoryStorage, test_suite::*, 6 | wasm_bindgen_test::*, 7 | }; 8 | 9 | wasm_bindgen_test_configure!(run_in_browser); 10 | 11 | struct CompositeTester { 12 | glue: Glue<CompositeStorage>, 13 | } 14 | 15 | #[async_trait(?Send)] 16 | impl Tester<CompositeStorage> for CompositeTester { 17 | async fn new(_: &str) -> Self { 18 | let mut storage = CompositeStorage::default(); 19 | storage.push("memory", MemoryStorage::default()); 20 | storage.set_default("memory"); 21 | 22 | let glue = Glue::new(storage); 23 | 24 | Self { glue } 25 | } 26 | 27 | fn get_glue(&mut self) -> &mut Glue<CompositeStorage> { 28 | &mut self.glue 29 | } 30 | } 31 | 32 | generate_store_tests!(wasm_bindgen_test, CompositeTester); 33 | -------------------------------------------------------------------------------- /pkg/javascript/webdriver.json: -------------------------------------------------------------------------------- 1 | { 2 | "moz:firefoxOptions": { 3 | "prefs": { 4 | "media.navigator.streams.fake": true, 5 | "media.navigator.permission.disabled": true 6 | }, 7 | "args": [] 8 | }, 9 | "goog:chromeOptions": { 10 | "args": [ 11 | "--use-fake-device-for-media-stream", 12 | "--use-fake-ui-for-media-stream" 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/python/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-py" 3 | authors = [ 4 | "Gyubong Lee <jopemachine@naver.com>", 5 | "Taehoon Moon <taehoon.moon@outlook.com>", 6 | ] 7 | version.workspace = true 8 | edition.workspace = true 9 | description.workspace = true 10 | license.workspace = true 11 | repository.workspace = true 12 | documentation.workspace = true 13 | 14 | [lib] 15 | name = "gluesql" 16 | crate-type = ["cdylib", "rlib"] 17 | 18 | [features] 19 | default = [] 20 | include-python-workspace = [] 21 | 22 | [dependencies] 23 | gluesql-core.workspace = true 24 | gluesql_memory_storage.workspace = true 25 | gluesql-web-storage.workspace = true 26 | gluesql-idb-storage.workspace = true 27 | gluesql-composite-storage.workspace = true 28 | gluesql_sled_storage.workspace = true 29 | gluesql-json-storage.workspace = true 30 | gluesql-shared-memory-storage.workspace = true 31 | 32 | pyo3 = { version = "0.19.2", features = ["extension-module"] } 33 | pythonize = "0.19.0" 34 | tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } 35 | serde = "1" 36 | serde_json = "1" 37 | gloo-utils = { version = "0.1.6", features = ["serde"] } 38 | 39 | [dev-dependencies] 40 | test-suite.workspace = true 41 | -------------------------------------------------------------------------------- /pkg/python/DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | ## 🚴 Usage 2 | 3 | ### Build 4 | 5 | To build `gluesql-py`, run below command. 6 | 7 | ``` 8 | maturin build 9 | ``` 10 | 11 | And to install the builded package, run below command. 12 | 13 | ``` 14 | pip install . 15 | ``` 16 | 17 | ### Test 18 | 19 | To run `gluesql-py` tests, run below command. 20 | 21 | ``` 22 | pytest 23 | ``` 24 | 25 | ## Deployment 26 | 27 | To build `gluesql-py` in release mode, run below command. 28 | 29 | ``` 30 | maturin build --release --strip 31 | ``` 32 | 33 | To deploy `gluesql-py` in [pypi](https://pypi.org/), run below command. 34 | 35 | ``` 36 | maturin publish 37 | ``` 38 | -------------------------------------------------------------------------------- /pkg/python/README.md: -------------------------------------------------------------------------------- 1 | # GlueSQL.py 2 | 3 | GlueSQL.py is a Python binding for the [GlueSQL](https://github.com/gluesql/gluesql) database engine. It provides an embedded SQL database that works with a selection of storage backends. 4 | 5 | Supported storages: 6 | 7 | - `MemoryStorage` 8 | - `JsonStorage` 9 | - `SharedMemoryStorage` 10 | - `SledStorage` 11 | 12 | Learn more at **<https://gluesql.org/docs>**. 13 | 14 | ## Installation 15 | 16 | Install from PyPI: 17 | 18 | ```bash 19 | pip install gluesql 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```python 25 | from gluesql import Glue, MemoryStorage 26 | 27 | storage = MemoryStorage() 28 | 29 | engine = Glue(storage) 30 | 31 | engine.query( 32 | """ 33 | CREATE TABLE User (id INTEGER, name TEXT); 34 | INSERT INTO User VALUES (1, 'Hello'), (2, 'World'); 35 | """ 36 | ) 37 | 38 | result = engine.query("SELECT * FROM User;") 39 | print(result) 40 | ``` 41 | 42 | ## License 43 | 44 | This project is licensed under the Apache License, Version 2.0 - see the [LICENSE](https://raw.githubusercontent.com/gluesql/gluesql/main/LICENSE) file for details. 45 | -------------------------------------------------------------------------------- /pkg/python/examples/json-storage/fixtures/test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Glue" 5 | }, 6 | { 7 | "id": 2, 8 | "name": "SQL" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /pkg/python/examples/memory-storage/main.py: -------------------------------------------------------------------------------- 1 | from gluesql import Glue, MemoryStorage 2 | from tabulate import tabulate 3 | 4 | db = Glue(MemoryStorage()) 5 | 6 | 7 | def run(): 8 | db.query( 9 | """ 10 | CREATE TABLE User (id INTEGER, name TEXT); 11 | CREATE TABLE Device (name TEXT, userId INTEGER); 12 | INSERT INTO User VALUES 13 | (1, 'glue'), (2, 'sticky'), (3, 'watt'); 14 | INSERT INTO Device VALUES 15 | ('Phone', 1), ('Mic', 1), ('Monitor', 3), 16 | ('Mouse', 2), ('Touchpad', 2); 17 | """ 18 | ) 19 | 20 | sql = "SHOW TABLES;" 21 | result = db.query(sql) 22 | tables = result[0].get("tables") 23 | tables = list(map(lambda t: [t], tables)) 24 | print(f"\n[Query]\n{sql}") 25 | print( 26 | tabulate(tables, headers=["Values"], showindex=True, tablefmt="simple_outline") 27 | ) 28 | 29 | sql = """ 30 | SELECT 31 | u.name as user, 32 | d.name as device 33 | FROM User u 34 | JOIN Device d ON u.id = d.userId 35 | """.strip().replace( 36 | " ", "" 37 | ) 38 | 39 | result = db.query(sql) 40 | rows = result[0].get("rows") 41 | print(f"\n[Query]\n{sql}") 42 | print(tabulate(rows, headers="keys", showindex=True, tablefmt="simple_outline")) 43 | 44 | 45 | if __name__ == "__main__": 46 | run() 47 | -------------------------------------------------------------------------------- /pkg/python/examples/shared-memory-storage/main.py: -------------------------------------------------------------------------------- 1 | from gluesql import Glue, SharedMemoryStorage 2 | from tabulate import tabulate 3 | 4 | db = Glue(SharedMemoryStorage()) 5 | 6 | 7 | def run(): 8 | db.query( 9 | """ 10 | CREATE TABLE User (id INTEGER, name TEXT); 11 | CREATE TABLE Device (name TEXT, userId INTEGER); 12 | INSERT INTO User VALUES 13 | (1, 'glue'), (2, 'sticky'), (3, 'watt'); 14 | INSERT INTO Device VALUES 15 | ('Phone', 1), ('Mic', 1), ('Monitor', 3), 16 | ('Mouse', 2), ('Touchpad', 2); 17 | """ 18 | ) 19 | 20 | sql = "SHOW TABLES;" 21 | result = db.query(sql) 22 | tables = result[0].get("tables") 23 | tables = list(map(lambda t: [t], tables)) 24 | print(f"\n[Query]\n{sql}") 25 | print( 26 | tabulate(tables, headers=["Values"], showindex=True, tablefmt="simple_outline") 27 | ) 28 | 29 | sql = """ 30 | SELECT 31 | u.name as user, 32 | d.name as device 33 | FROM User u 34 | JOIN Device d ON u.id = d.userId 35 | """.strip().replace( 36 | " ", "" 37 | ) 38 | 39 | result = db.query(sql) 40 | rows = result[0].get("rows") 41 | print(f"\n[Query]\n{sql}") 42 | print(tabulate(rows, headers="keys", showindex=True, tablefmt="simple_outline")) 43 | 44 | 45 | if __name__ == "__main__": 46 | run() 47 | -------------------------------------------------------------------------------- /pkg/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.0,<2.0"] 3 | build-backend = "maturin" 4 | 5 | [tool.maturin] 6 | bindings = "pyo3" 7 | features = ["include-python-workspace"] 8 | 9 | [project] 10 | name = "gluesql" 11 | requires-python = ">=3.10" 12 | description = "GlueSQL is quite sticky. It attaches to anywhere." 13 | readme = "README.md" 14 | classifiers = [ 15 | "Programming Language :: Rust", 16 | "Programming Language :: Python :: Implementation :: CPython", 17 | ] 18 | -------------------------------------------------------------------------------- /pkg/python/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = tests/*.py 3 | -------------------------------------------------------------------------------- /pkg/python/requirements.txt: -------------------------------------------------------------------------------- 1 | maturin==1.2.3 2 | pytest==7.3.1 3 | tabulate==0.9.0 -------------------------------------------------------------------------------- /pkg/python/src/error.rs: -------------------------------------------------------------------------------- 1 | use { 2 | gluesql_core::prelude::Error, 3 | pyo3::{create_exception, exceptions::PyException, prelude::*, pyclass::CompareOp}, 4 | }; 5 | 6 | #[pyclass(name = "GlueSQLError")] 7 | pub struct PyGlueSQLError(pub Error); 8 | 9 | create_exception!(gluesql, GlueSQLError, PyException); 10 | 11 | #[pymethods] 12 | impl PyGlueSQLError { 13 | pub fn __richcmp__(&self, py: Python, rhs: &PyGlueSQLError, op: CompareOp) -> PyObject { 14 | match op { 15 | CompareOp::Eq => (self.0 == rhs.0).into_py(py), 16 | CompareOp::Ne => (self.0 != rhs.0).into_py(py), 17 | _ => py.NotImplemented(), 18 | } 19 | } 20 | 21 | pub fn __repr__(&self) -> String { 22 | format!("{}", self.0) 23 | } 24 | } 25 | 26 | impl From<PyGlueSQLError> for PyErr { 27 | fn from(e: PyGlueSQLError) -> PyErr { 28 | GlueSQLError::new_err(e.0.to_string()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/rust/examples/api_usage.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "gluesql_sled_storage")] 2 | mod api_usage { 3 | use gluesql::prelude::{Glue, SledStorage}; 4 | 5 | pub async fn run() { 6 | let storage = SledStorage::new("data/mutable-api").unwrap(); 7 | let mut glue = Glue::new(storage); 8 | 9 | let sqls = [ 10 | "CREATE TABLE Glue (id INTEGER);", 11 | "INSERT INTO Glue VALUES (100);", 12 | "INSERT INTO Glue VALUES (200);", 13 | "DROP TABLE Glue;", 14 | ]; 15 | 16 | for sql in sqls { 17 | glue.execute(sql).await.unwrap(); 18 | } 19 | } 20 | } 21 | 22 | fn main() { 23 | #[cfg(feature = "gluesql_sled_storage")] 24 | futures::executor::block_on(api_usage::run()); 25 | } 26 | -------------------------------------------------------------------------------- /pkg/rust/examples/memory_storage_usage.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "gluesql_memory_storage")] 2 | mod api_usage { 3 | use gluesql::{gluesql_memory_storage::MemoryStorage, prelude::Glue}; 4 | 5 | pub async fn run() { 6 | let storage = MemoryStorage::default(); 7 | let mut glue = Glue::new(storage); 8 | 9 | glue.execute("DROP TABLE IF EXISTS api_test").await.unwrap(); 10 | 11 | glue.execute( 12 | "CREATE TABLE api_test ( 13 | id INTEGER, 14 | name TEXT, 15 | nullable TEXT NULL, 16 | is BOOLEAN 17 | )", 18 | ) 19 | .await 20 | .unwrap(); 21 | 22 | glue.execute( 23 | "INSERT INTO api_test ( 24 | id, 25 | name, 26 | nullable, 27 | is 28 | ) VALUES 29 | (1, 'test1', 'not null', TRUE), 30 | (2, 'test2', NULL, FALSE)", 31 | ) 32 | .await 33 | .unwrap(); 34 | } 35 | } 36 | 37 | fn main() { 38 | #[cfg(feature = "gluesql_memory_storage")] 39 | futures::executor::block_on(api_usage::run()); 40 | } 41 | -------------------------------------------------------------------------------- /pkg/rust/examples/using_config.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "gluesql_sled_storage")] 2 | use { 3 | futures::executor::block_on, 4 | gluesql::{gluesql_sled_storage::SledStorage, prelude::Glue}, 5 | gluesql_sled_storage::sled, 6 | std::convert::TryFrom, 7 | }; 8 | 9 | fn main() { 10 | #[cfg(feature = "gluesql_sled_storage")] 11 | { 12 | let config = sled::Config::default() 13 | .path("data/using_config") 14 | .temporary(true) 15 | .mode(sled::Mode::HighThroughput); 16 | 17 | let storage = SledStorage::try_from(config).unwrap(); 18 | 19 | let mut glue = Glue::new(storage); 20 | 21 | let sqls = " 22 | CREATE TABLE Glue (id INTEGER); 23 | INSERT INTO Glue VALUES (100); 24 | INSERT INTO Glue VALUES (200); 25 | DROP TABLE Glue; 26 | "; 27 | 28 | block_on(glue.execute(sqls)).unwrap(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(feature = "cli")] 3 | cli::run().unwrap(); 4 | } 5 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.87" 3 | components = ["rustfmt", "clippy"] 4 | -------------------------------------------------------------------------------- /storages/composite-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-composite-storage" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | async-trait = "0.1" 14 | serde = { version = "1", features = ["derive"] } 15 | futures = "0.3" 16 | 17 | [dev-dependencies] 18 | test-suite.workspace = true 19 | gluesql_memory_storage.workspace = true 20 | gluesql_sled_storage.workspace = true 21 | 22 | tokio = { version = "1", features = ["rt", "macros"] } 23 | -------------------------------------------------------------------------------- /storages/composite-storage/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::CompositeStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | error::{Error, Result}, 6 | store::Transaction, 7 | }, 8 | }; 9 | 10 | #[async_trait(?Send)] 11 | impl Transaction for CompositeStorage { 12 | async fn begin(&mut self, autocommit: bool) -> Result<bool> { 13 | if autocommit { 14 | for storage in self.storages.values_mut() { 15 | storage.begin(autocommit).await?; 16 | } 17 | 18 | return Ok(true); 19 | } 20 | 21 | Err(Error::StorageMsg( 22 | "[CompositeStorage] Transaction::begin is not supported".to_owned(), 23 | )) 24 | } 25 | 26 | async fn rollback(&mut self) -> Result<()> { 27 | for storage in self.storages.values_mut() { 28 | storage.commit().await?; 29 | } 30 | 31 | Ok(()) 32 | } 33 | 34 | async fn commit(&mut self) -> Result<()> { 35 | for storage in self.storages.values_mut() { 36 | storage.commit().await?; 37 | } 38 | 39 | Ok(()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /storages/composite-storage/tests/composite_storage.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, gluesql_composite_storage::CompositeStorage, 3 | gluesql_core::prelude::Glue, gluesql_memory_storage::MemoryStorage, test_suite::*, 4 | }; 5 | 6 | struct CompositeTester { 7 | glue: Glue<CompositeStorage>, 8 | } 9 | 10 | #[async_trait(?Send)] 11 | impl Tester<CompositeStorage> for CompositeTester { 12 | async fn new(_: &str) -> Self { 13 | let mut storage = CompositeStorage::default(); 14 | storage.push("MEMORY", MemoryStorage::default()); 15 | storage.set_default("MEMORY"); 16 | 17 | let glue = Glue::new(storage); 18 | 19 | Self { glue } 20 | } 21 | 22 | fn get_glue(&mut self) -> &mut Glue<CompositeStorage> { 23 | &mut self.glue 24 | } 25 | } 26 | 27 | generate_store_tests!(tokio::test, CompositeTester); 28 | generate_alter_table_tests!(tokio::test, CompositeTester); 29 | -------------------------------------------------------------------------------- /storages/csv-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-csv-storage" 3 | authors = [ 4 | "Taehoon Moon <taehoon.moon@outlook.com>", 5 | ] 6 | version.workspace = true 7 | edition.workspace = true 8 | description.workspace = true 9 | license.workspace = true 10 | repository.workspace = true 11 | documentation.workspace = true 12 | 13 | [dependencies] 14 | gluesql-core.workspace = true 15 | utils.workspace = true 16 | 17 | async-trait = "0.1" 18 | serde = { version = "1", features = ["derive"] } 19 | serde_json = "1" 20 | thiserror = "1.0" 21 | csv = "1.2.2" 22 | futures = "0.3" 23 | 24 | [dev-dependencies] 25 | test-suite.workspace = true 26 | 27 | tokio = { version = "1", features = ["rt", "macros"] } 28 | rust_decimal = "1" 29 | rust_decimal_macros = "1" 30 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/csv_storage.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, gluesql_core::prelude::Glue, gluesql_csv_storage::CsvStorage, 3 | std::fs::remove_dir_all, test_suite::*, 4 | }; 5 | 6 | struct CsvTester { 7 | glue: Glue<CsvStorage>, 8 | } 9 | 10 | #[async_trait(?Send)] 11 | impl Tester<CsvStorage> for CsvTester { 12 | async fn new(namespace: &str) -> Self { 13 | let path = format!("tmp/{namespace}"); 14 | 15 | if let Err(e) = remove_dir_all(&path) { 16 | println!("fs::remove_file {:?}", e); 17 | }; 18 | 19 | let storage = CsvStorage::new(&path).expect("CsvStorage::new"); 20 | let glue = Glue::new(storage); 21 | CsvTester { glue } 22 | } 23 | 24 | fn get_glue(&mut self) -> &mut Glue<CsvStorage> { 25 | &mut self.glue 26 | } 27 | } 28 | 29 | generate_store_tests!(tokio::test, CsvTester); 30 | generate_alter_table_tests!(tokio::test, CsvTester); 31 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/error.rs: -------------------------------------------------------------------------------- 1 | use { 2 | gluesql_core::prelude::Glue, 3 | gluesql_csv_storage::{CsvStorage, error::CsvStorageError}, 4 | }; 5 | 6 | #[tokio::test] 7 | async fn error() { 8 | let path = "./tests/samples/"; 9 | let storage = CsvStorage::new(path).unwrap(); 10 | let mut glue = Glue::new(storage); 11 | 12 | let actual = glue.execute("SELECT * FROM WrongSchemaName").await; 13 | let expected = Err(CsvStorageError::TableNameDoesNotMatchWithFile.into()); 14 | assert_eq!(actual, expected); 15 | } 16 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/Book.csv: -------------------------------------------------------------------------------- 1 | Author,Genre,Price,Publisher,Title,Year 2 | Harper Lee,,,J. B. Lippincott & Co.,To Kill a Mockingbird,1960 3 | F. Scott Fitzgerald,Classic,9.99,,The Great Gatsby,1925 4 | George Orwell,Dystopian,7.99,Secker & Warburg,1984, 5 | J. D. Salinger,Coming-of-age,6.99,"Little, Brown and Company",The Catcher in the Rye,1951 6 | Jane Austen,Romance,,,Pride and Prejudice,1813 7 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/Book.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "Book"; 2 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/City.csv: -------------------------------------------------------------------------------- 1 | Country,City,Population 2 | South Korea,Seoul,9736962 3 | Japan,Tokyo,13515271 4 | China,Shanghai,24281000 5 | United States,New York City,8336817 6 | Italy,Milan,2837332 7 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/City.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE City (Country TEXT NOT NULL, City TEXT NOT NULL, Population INT NOT NULL); 2 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/Employee.csv: -------------------------------------------------------------------------------- 1 | Name,Age,Gender,Occupation 2 | John,25,Male,Engineer 3 | Sarah,30,Female,Doctor 4 | Michael,40,Male,Lawyer 5 | Emily,28,Female,Teacher 6 | David,35,Male,Programmer -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/Grocery.csv: -------------------------------------------------------------------------------- 1 | Product Name,Price,Quantity,Manufacturer,Expiration Date 2 | Apple,1.99,100,Apple Inc.,2022-08-31 3 | Banana,0.99,50,Chiquita Brands LLC,2022-09-01 4 | Milk,2.49,,Dean Foods Company,2022-08-30 5 | Bread,1.99,30,Sara Lee Corporation, 6 | Eggs,2.99,40,,2022-09-02 7 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/Grocery.types.csv: -------------------------------------------------------------------------------- 1 | Product Name,Price,Quantity,Manufacturer,Expiration Date 2 | TEXT,DECIMAL,INT,TEXT,DATE 3 | TEXT,DECIMAL,INT,TEXT,DATE 4 | TEXT,DECIMAL,,TEXT,DATE 5 | TEXT,DECIMAL,INT,TEXT,NULL 6 | TEXT,DECIMAL,INT,,DATE 7 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/Student.csv: -------------------------------------------------------------------------------- 1 | Name,Gender,Age,Grade 2 | John,Male,18,A 3 | Jane,Female,17,B 4 | Bob,NULL,NULL,C 5 | Alice,Female,18,NULL 6 | Mike,Male,NULL,B 7 | Lisa,NULL,18,A 8 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/Student.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "Student"; 2 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/Student.types.csv: -------------------------------------------------------------------------------- 1 | Name,Gender,Age,Grade 2 | TEXT,TEXT,INT,TEXT 3 | TEXT,TEXT,INT,TEXT 4 | TEXT,,,TEXT 5 | TEXT,TEXT,INT, 6 | TEXT,TEXT,,TEXT 7 | TEXT,,INT,TEXT 8 | -------------------------------------------------------------------------------- /storages/csv-storage/tests/samples/WrongSchemaName.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE AnotherSchemaName; 2 | -------------------------------------------------------------------------------- /storages/file-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-file-storage" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | async-trait = "0.1" 14 | serde = { version = "1", features = ["derive"] } 15 | futures = "0.3" 16 | uuid = { version = "1.10.0", features = ["v7"] } 17 | hex = "0.4.3" 18 | ron = { version = "0.8.1", features = ["integer128"] } 19 | 20 | [dev-dependencies] 21 | test-suite.workspace = true 22 | tokio = { version = "1", features = ["rt", "macros"] } 23 | -------------------------------------------------------------------------------- /storages/git-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-git-storage" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | gluesql-file-storage.workspace = true 14 | gluesql-csv-storage.workspace = true 15 | gluesql-json-storage.workspace = true 16 | async-trait = "0.1" 17 | strum_macros = "0.26.4" 18 | 19 | [dev-dependencies] 20 | test-suite.workspace = true 21 | tokio = { version = "1", features = ["rt", "macros"] } 22 | uuid = { version = "1.10.0", features = ["v7"] } 23 | 24 | [features] 25 | test-git-remote = [] 26 | -------------------------------------------------------------------------------- /storages/git-storage/src/store.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::GitStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | data::{Key, Schema}, 6 | error::Result, 7 | store::{DataRow, RowIter, Store}, 8 | }, 9 | }; 10 | 11 | #[async_trait(?Send)] 12 | impl Store for GitStorage { 13 | async fn fetch_all_schemas(&self) -> Result<Vec<Schema>> { 14 | self.get_store().fetch_all_schemas().await 15 | } 16 | 17 | async fn fetch_schema(&self, table_name: &str) -> Result<Option<Schema>> { 18 | self.get_store().fetch_schema(table_name).await 19 | } 20 | 21 | async fn fetch_data(&self, table_name: &str, key: &Key) -> Result<Option<DataRow>> { 22 | self.get_store().fetch_data(table_name, key).await 23 | } 24 | 25 | async fn scan_data<'a>(&'a self, table_name: &str) -> Result<RowIter<'a>> { 26 | self.get_store().scan_data(table_name).await 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /storages/git-storage/tests/git_storage_csv.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, 3 | gluesql_core::prelude::Glue, 4 | gluesql_git_storage::{GitStorage, StorageType}, 5 | std::fs::remove_dir_all, 6 | test_suite::*, 7 | }; 8 | 9 | struct GitStorageTester { 10 | glue: Glue<GitStorage>, 11 | } 12 | 13 | #[async_trait(?Send)] 14 | impl Tester<GitStorage> for GitStorageTester { 15 | async fn new(namespace: &str) -> Self { 16 | let path = format!("tmp/git_storage_csv/{namespace}"); 17 | 18 | if let Err(e) = remove_dir_all(&path) { 19 | println!("fs::remove_file {:?}", e); 20 | }; 21 | 22 | let storage = GitStorage::init(&path, StorageType::Csv).expect("GitStorage::init - CSV"); 23 | let glue = Glue::new(storage); 24 | GitStorageTester { glue } 25 | } 26 | 27 | fn get_glue(&mut self) -> &mut Glue<GitStorage> { 28 | &mut self.glue 29 | } 30 | } 31 | 32 | generate_store_tests!(tokio::test, GitStorageTester); 33 | generate_alter_table_tests!(tokio::test, GitStorageTester); 34 | -------------------------------------------------------------------------------- /storages/git-storage/tests/git_storage_file.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, 3 | gluesql_core::prelude::Glue, 4 | gluesql_git_storage::{GitStorage, StorageType}, 5 | std::fs::remove_dir_all, 6 | test_suite::*, 7 | }; 8 | 9 | struct GitStorageTester { 10 | glue: Glue<GitStorage>, 11 | } 12 | 13 | #[async_trait(?Send)] 14 | impl Tester<GitStorage> for GitStorageTester { 15 | async fn new(namespace: &str) -> Self { 16 | let path = format!("tmp/git_storage_file/{namespace}"); 17 | 18 | if let Err(e) = remove_dir_all(&path) { 19 | println!("fs::remove_file {:?}", e); 20 | }; 21 | 22 | let storage = GitStorage::init(&path, StorageType::File).expect("GitStorage::init - File"); 23 | let glue = Glue::new(storage); 24 | GitStorageTester { glue } 25 | } 26 | 27 | fn get_glue(&mut self) -> &mut Glue<GitStorage> { 28 | &mut self.glue 29 | } 30 | } 31 | 32 | generate_store_tests!(tokio::test, GitStorageTester); 33 | generate_alter_table_tests!(tokio::test, GitStorageTester); 34 | -------------------------------------------------------------------------------- /storages/git-storage/tests/git_storage_json.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, 3 | gluesql_core::prelude::Glue, 4 | gluesql_git_storage::{GitStorage, StorageType}, 5 | std::fs::remove_dir_all, 6 | test_suite::*, 7 | }; 8 | 9 | struct GitStorageTester { 10 | glue: Glue<GitStorage>, 11 | } 12 | 13 | #[async_trait(?Send)] 14 | impl Tester<GitStorage> for GitStorageTester { 15 | async fn new(namespace: &str) -> Self { 16 | let path = format!("tmp/git_storage_json/{namespace}"); 17 | 18 | if let Err(e) = remove_dir_all(&path) { 19 | println!("fs::remove_file {:?}", e); 20 | }; 21 | 22 | let storage = GitStorage::init(&path, StorageType::Json).expect("GitStorage::init - JSON"); 23 | let glue = Glue::new(storage); 24 | GitStorageTester { glue } 25 | } 26 | 27 | fn get_glue(&mut self) -> &mut Glue<GitStorage> { 28 | &mut self.glue 29 | } 30 | } 31 | 32 | generate_store_tests!(tokio::test, GitStorageTester); 33 | generate_alter_table_tests!(tokio::test, GitStorageTester); 34 | -------------------------------------------------------------------------------- /storages/idb-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-idb-storage" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | async-trait = "0.1" 14 | serde = { version = "1", features = ["derive"] } 15 | idb = "0.6.1" 16 | serde-wasm-bindgen = "0.6.3" 17 | wasm-bindgen = "0.2.90" 18 | web-sys = { version = "0.3.67", features = ["console"] } 19 | serde_json = "1.0.111" 20 | gloo-utils = { version = "0.2.0", features = ["serde"] } 21 | futures = "0.3" 22 | 23 | [dev-dependencies] 24 | test-suite.workspace = true 25 | wasm-bindgen-test = "0.3.40" 26 | -------------------------------------------------------------------------------- /storages/idb-storage/README.md: -------------------------------------------------------------------------------- 1 | ## 🚴 IdbStorage - IndexedDB storage support for GlueSQL 2 | 3 | ### 🔬 Test in Headless Browsers with `wasm-pack test` 4 | ``` 5 | WASM_BINDGEN_TEST_TIMEOUT=60 wasm-pack test --headless --firefox --chrome 6 | ``` 7 | -------------------------------------------------------------------------------- /storages/idb-storage/src/error.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, 3 | core::fmt::Display, 4 | gluesql_core::error::{Error, Result}, 5 | std::{future::IntoFuture, result::Result as StdResult}, 6 | }; 7 | 8 | pub trait ErrInto<T> { 9 | fn err_into(self) -> Result<T>; 10 | } 11 | 12 | impl<T, E: Display> ErrInto<T> for Result<T, E> { 13 | fn err_into(self) -> Result<T> { 14 | self.map_err(|e| Error::StorageMsg(e.to_string())) 15 | } 16 | } 17 | 18 | #[async_trait(?Send)] 19 | pub trait StoreReqIntoFuture<T> { 20 | async fn into_future(self) -> Result<T>; 21 | } 22 | 23 | #[async_trait(?Send)] 24 | impl<F, T, E: Display> StoreReqIntoFuture<T> for Result<F, E> 25 | where 26 | F: IntoFuture<Output = StdResult<T, E>>, 27 | { 28 | async fn into_future(self) -> Result<T> { 29 | self.err_into()?.await.err_into() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /storages/idb-storage/tests/idb_storage.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "wasm32")] 2 | 3 | use { 4 | async_trait::async_trait, 5 | gluesql_core::prelude::Glue, 6 | gluesql_idb_storage::IdbStorage, 7 | test_suite::*, 8 | wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}, 9 | }; 10 | 11 | wasm_bindgen_test_configure!(run_in_browser); 12 | 13 | struct IdbStorageTester { 14 | glue: Glue<IdbStorage>, 15 | } 16 | 17 | #[async_trait(?Send)] 18 | impl Tester<IdbStorage> for IdbStorageTester { 19 | async fn new(namespace: &str) -> Self { 20 | let storage = IdbStorage::new(Some(namespace.to_owned())).await.unwrap(); 21 | let glue = Glue::new(storage); 22 | 23 | Self { glue } 24 | } 25 | 26 | fn get_glue(&mut self) -> &mut Glue<IdbStorage> { 27 | &mut self.glue 28 | } 29 | } 30 | 31 | generate_store_tests!(wasm_bindgen_test, IdbStorageTester); 32 | generate_alter_table_tests!(wasm_bindgen_test, IdbStorageTester); 33 | -------------------------------------------------------------------------------- /storages/json-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-json-storage" 3 | authors = [ 4 | "Hyoungkwan Cho <devgony@gmail.com>", 5 | "Taehoon Moon <taehoon.moon@outlook.com>", 6 | ] 7 | version.workspace = true 8 | edition.workspace = true 9 | description.workspace = true 10 | license.workspace = true 11 | repository.workspace = true 12 | documentation.workspace = true 13 | 14 | [dependencies] 15 | gluesql-core.workspace = true 16 | utils.workspace = true 17 | 18 | async-trait = "0.1" 19 | serde = { version = "1", features = ["derive"] } 20 | serde_json = "1" 21 | futures = "0.3" 22 | tokio = { version = "1", features = ["rt", "macros"] } 23 | hex = "0.4" 24 | thiserror = "1.0" 25 | iter-enum = "1" 26 | 27 | [dev-dependencies] 28 | test-suite.workspace = true 29 | uuid = "1" 30 | -------------------------------------------------------------------------------- /storages/json-storage/src/alter_table.rs: -------------------------------------------------------------------------------- 1 | use {super::JsonStorage, gluesql_core::store::AlterTable}; 2 | 3 | impl AlterTable for JsonStorage {} 4 | -------------------------------------------------------------------------------- /storages/json-storage/src/function.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::JsonStorage, 3 | gluesql_core::store::{CustomFunction, CustomFunctionMut}, 4 | }; 5 | 6 | impl CustomFunction for JsonStorage {} 7 | impl CustomFunctionMut for JsonStorage {} 8 | -------------------------------------------------------------------------------- /storages/json-storage/src/index.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::JsonStorage, 3 | gluesql_core::store::{Index, IndexMut}, 4 | }; 5 | 6 | impl Index for JsonStorage {} 7 | impl IndexMut for JsonStorage {} 8 | -------------------------------------------------------------------------------- /storages/json-storage/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use {super::JsonStorage, gluesql_core::store::Transaction}; 2 | 3 | impl Transaction for JsonStorage {} 4 | -------------------------------------------------------------------------------- /storages/json-storage/tests/json_storage.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, gluesql_core::prelude::Glue, gluesql_json_storage::JsonStorage, 3 | std::fs::remove_dir_all, test_suite::*, 4 | }; 5 | 6 | struct JsonTester { 7 | glue: Glue<JsonStorage>, 8 | } 9 | 10 | #[async_trait(?Send)] 11 | impl Tester<JsonStorage> for JsonTester { 12 | async fn new(namespace: &str) -> Self { 13 | let path = format!("tmp/{namespace}"); 14 | 15 | if let Err(e) = remove_dir_all(&path) { 16 | println!("fs::remove_file {:?}", e); 17 | }; 18 | 19 | let storage = JsonStorage::new(&path).expect("JsonStorage::new"); 20 | let glue = Glue::new(storage); 21 | JsonTester { glue } 22 | } 23 | 24 | fn get_glue(&mut self) -> &mut Glue<JsonStorage> { 25 | &mut self.glue 26 | } 27 | } 28 | 29 | generate_store_tests!(tokio::test, JsonTester); 30 | generate_alter_table_tests!(tokio::test, JsonTester); 31 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/ArrayOfJsonsSchema.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Glue" 5 | }, 6 | { 7 | "id": 2, 8 | "name": "SQL" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/ArrayOfJsonsSchema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE ArrayOfJsonsSchema ( 2 | id INT, 3 | name TEXT 4 | ); 5 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/ArrayOfJsonsSchemaless.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Glue" 5 | }, 6 | { 7 | "id": 2, 8 | "name": "SQL" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/Duplicated.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "notice": "This file exists to throw error if both *.jsonl and *.json file exist with identical name" 4 | } 5 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/Duplicated.jsonl: -------------------------------------------------------------------------------- 1 | {"id": 1, "notice": "This file exists to throw error if both *.jsonl and *.json file exist with identical name"} 2 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/JsonArrayTypeRequired.json: -------------------------------------------------------------------------------- 1 | "neither Array of jsons nor a single Json" 2 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/JsonObjectTypeRequired.json: -------------------------------------------------------------------------------- 1 | [ 2 | "Not a Json" 3 | ] 4 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/Schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE Schema ( 2 | boolean BOOLEAN, 3 | int8 INT8, 4 | int16 INT16, 5 | int32 INT32, 6 | int64 INT, 7 | uint8 UINT8, 8 | text TEXT, 9 | bytea BYTEA, 10 | inet INET, 11 | date DATE, 12 | timestamp TIMESTAMP, 13 | time TIME, 14 | interval INTERVAL, 15 | uuid UUID, 16 | map MAP, 17 | list LIST 18 | ); 19 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/Schemaless.jsonl: -------------------------------------------------------------------------------- 1 | {"id": 1} 2 | {"name": "Glue"} 3 | {"id": 3, "name": "SQL"} 4 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/SingleJsonSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 1, 5 | "name": "Glue" 6 | }, 7 | { 8 | "id": 2, 9 | "name": "SQL" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/SingleJsonSchema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE SingleJsonSchema ( 2 | data LIST 3 | ); 4 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/SingleJsonSchemaless.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 1, 5 | "name": "Glue" 6 | }, 7 | { 8 | "id": 2, 9 | "name": "SQL" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/WrongFormatJson.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "notice": "*.json usage1: An array of jsons" 4 | }, 5 | { 6 | "id": 2, 7 | "notice": "*.json usage2: A single json in a file" 8 | } 9 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/WrongFormatJsonl.jsonl: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "notice": "*.jsonl is a newline-delimited JSON file" 5 | }, 6 | { 7 | "id": 2, 8 | "notice": "Only a single json must be written to a single line" 9 | }, 10 | { 11 | "id": 3, 12 | "notice": "To use this array of jsons format, use *.json extension" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/WrongSchema.jsonl: -------------------------------------------------------------------------------- 1 | {"id": 1, "notice": "This file exists to test WrongSchema.sql file"} 2 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/WrongSchema.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE WrongSchema ADD COLUMN dostNotWork INT; 2 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/WrongTableName.jsonl: -------------------------------------------------------------------------------- 1 | {"id": 1, "notice": "This file exists to test WrongTableName.sql file"} 2 | -------------------------------------------------------------------------------- /storages/json-storage/tests/samples/WrongTableName.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE TableNameDoesNotMatchWithFileName ( 2 | id int 3 | ); 4 | -------------------------------------------------------------------------------- /storages/memory-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql_memory_storage" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | async-trait = "0.1" 14 | serde = { version = "1", features = ["derive"] } 15 | futures = "0.3" 16 | 17 | [dev-dependencies] 18 | test-suite.workspace = true 19 | tokio = { version = "1", features = ["rt", "macros"] } 20 | -------------------------------------------------------------------------------- /storages/memory-storage/src/index.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::MemoryStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | ast::{IndexOperator, OrderByExpr}, 6 | data::Value, 7 | error::{Error, Result}, 8 | store::{Index, IndexMut, RowIter}, 9 | }, 10 | }; 11 | 12 | #[async_trait(?Send)] 13 | impl Index for MemoryStorage { 14 | async fn scan_indexed_data<'a>( 15 | &'a self, 16 | _table_name: &str, 17 | _index_name: &str, 18 | _asc: Option<bool>, 19 | _cmp_value: Option<(&IndexOperator, Value)>, 20 | ) -> Result<RowIter<'a>> { 21 | Err(Error::StorageMsg( 22 | "[MemoryStorage] index is not supported".to_owned(), 23 | )) 24 | } 25 | } 26 | 27 | #[async_trait(?Send)] 28 | impl IndexMut for MemoryStorage { 29 | async fn create_index( 30 | &mut self, 31 | _table_name: &str, 32 | _index_name: &str, 33 | _column: &OrderByExpr, 34 | ) -> Result<()> { 35 | Err(Error::StorageMsg( 36 | "[MemoryStorage] index is not supported".to_owned(), 37 | )) 38 | } 39 | 40 | async fn drop_index(&mut self, _table_name: &str, _index_name: &str) -> Result<()> { 41 | Err(Error::StorageMsg( 42 | "[MemoryStorage] index is not supported".to_owned(), 43 | )) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /storages/memory-storage/src/metadata.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::MemoryStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | error::Result, 6 | store::{MetaIter, Metadata}, 7 | }, 8 | }; 9 | 10 | #[async_trait(?Send)] 11 | impl Metadata for MemoryStorage { 12 | async fn scan_table_meta(&self) -> Result<MetaIter> { 13 | let meta = self.metadata.clone().into_iter().map(Ok); 14 | 15 | Ok(Box::new(meta)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /storages/memory-storage/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::MemoryStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | error::{Error, Result}, 6 | store::Transaction, 7 | }, 8 | }; 9 | 10 | #[async_trait(?Send)] 11 | impl Transaction for MemoryStorage { 12 | async fn begin(&mut self, autocommit: bool) -> Result<bool> { 13 | if autocommit { 14 | return Ok(false); 15 | } 16 | 17 | Err(Error::StorageMsg( 18 | "[MemoryStorage] transaction is not supported".to_owned(), 19 | )) 20 | } 21 | 22 | async fn rollback(&mut self) -> Result<()> { 23 | Ok(()) 24 | } 25 | 26 | async fn commit(&mut self) -> Result<()> { 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /storages/mongo-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-mongo-storage" 3 | authors = [ 4 | "Hyoungkwan Cho <devgony@gmail.com>", 5 | "Taehoon Moon <taehoon.moon@outlook.com>", 6 | ] 7 | version.workspace = true 8 | edition.workspace = true 9 | description.workspace = true 10 | license.workspace = true 11 | repository.workspace = true 12 | documentation.workspace = true 13 | 14 | [dependencies] 15 | gluesql-core.workspace = true 16 | 17 | async-trait = "0.1" 18 | futures = "0.3" 19 | thiserror = "1.0" 20 | mongodb = "2.5.0" 21 | bson = { version = "2.6.1", features = ["chrono-0_4"] } 22 | chrono = { version = "0.4.26", features = ["serde", "wasmbind"] } 23 | strum_macros = "0.24" 24 | strum = "0.24" 25 | rust_decimal = { version = "1", features = ["serde-str"] } 26 | serde_json = "1.0.115" 27 | serde = "1.0.197" 28 | 29 | [dev-dependencies] 30 | test-suite.workspace = true 31 | tokio = { version = "1", features = ["rt", "macros"] } 32 | 33 | [features] 34 | test-mongo = [] 35 | -------------------------------------------------------------------------------- /storages/mongo-storage/README.md: -------------------------------------------------------------------------------- 1 | ## 🚴 MongoStorage - Mongo storage support for GlueSQL 2 | 3 | ### ⚙️ Prerequisites 4 | 5 | Install & start up MongoDB 6 | 7 | #### 1. By Docker 8 | 9 | ##### 1-1) Install docker 10 | 11 | https://docs.docker.com/engine/install/ 12 | 13 | ##### 1-2) Start up MongoDB by docker 14 | 15 | ``` 16 | docker run --name mongo-glue -d -p 27017:27017 mongo 17 | ``` 18 | 19 | #### 2. By local installation 20 | 21 | https://www.mongodb.com/docs/manual/installation/ 22 | 23 | ### 🧪 Test with features 24 | 25 | ``` 26 | cargo test --features test-mongo 27 | ``` 28 | -------------------------------------------------------------------------------- /storages/mongo-storage/src/description.rs: -------------------------------------------------------------------------------- 1 | use { 2 | gluesql_core::ast::{Expr, ForeignKey}, 3 | serde::{Deserialize, Serialize}, 4 | }; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | pub struct TableDescription { 8 | pub foreign_keys: Vec<ForeignKey>, 9 | pub comment: Option<String>, 10 | } 11 | 12 | #[derive(Serialize, Deserialize)] 13 | pub struct ColumnDescription { 14 | pub default: Option<Expr>, 15 | pub comment: Option<String>, 16 | } 17 | -------------------------------------------------------------------------------- /storages/mongo-storage/src/error.rs: -------------------------------------------------------------------------------- 1 | use {gluesql_core::error::Error, thiserror::Error}; 2 | 3 | pub trait ResultExt<T, E: ToString> { 4 | fn map_storage_err(self) -> Result<T, Error>; 5 | } 6 | 7 | impl<T, E: ToString> ResultExt<T, E> for std::result::Result<T, E> { 8 | fn map_storage_err(self) -> Result<T, Error> { 9 | self.map_err(|e| e.to_string()).map_err(Error::StorageMsg) 10 | } 11 | } 12 | 13 | pub trait OptionExt<T, E: ToString> { 14 | fn map_storage_err(self, error: E) -> Result<T, Error>; 15 | } 16 | 17 | impl<T, E: ToString> OptionExt<T, E> for std::option::Option<T> { 18 | fn map_storage_err(self, error: E) -> Result<T, Error> { 19 | self.ok_or_else(|| error.to_string()) 20 | .map_err(Error::StorageMsg) 21 | } 22 | } 23 | 24 | #[derive(Error, Debug)] 25 | pub enum MongoStorageError { 26 | #[error("invalid document")] 27 | InvalidDocument, 28 | 29 | #[error("unreachable")] 30 | Unreachable, 31 | 32 | #[error("unsupported bson type")] 33 | UnsupportedBsonType, 34 | 35 | #[error(r#"Invalid bsonType - it should be Array eg) ["string"] or ["string", "null"]"#)] 36 | InvalidBsonType, 37 | 38 | #[error("Invalid glueType - it should be type of GlueSQL Value")] 39 | InvalidGlueType, 40 | } 41 | -------------------------------------------------------------------------------- /storages/mongo-storage/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod description; 2 | pub mod error; 3 | pub mod row; 4 | mod store; 5 | mod store_mut; 6 | pub mod utils; 7 | 8 | use { 9 | error::ResultExt, 10 | gluesql_core::{ 11 | error::Result, 12 | store::{ 13 | AlterTable, CustomFunction, CustomFunctionMut, Index, IndexMut, Metadata, Transaction, 14 | }, 15 | }, 16 | mongodb::{Client, Database, options::ClientOptions}, 17 | }; 18 | 19 | pub struct MongoStorage { 20 | pub db: Database, 21 | } 22 | 23 | impl MongoStorage { 24 | pub async fn new(conn_str: &str, db_name: &str) -> Result<Self> { 25 | let client_options = ClientOptions::parse(conn_str).await.map_storage_err()?; 26 | let client = Client::with_options(client_options).map_storage_err()?; 27 | let db = client.database(db_name); 28 | 29 | Ok(Self { db }) 30 | } 31 | 32 | pub async fn drop_database(&self) -> Result<()> { 33 | self.db.drop(None).await.map_storage_err() 34 | } 35 | } 36 | 37 | impl Metadata for MongoStorage {} 38 | impl AlterTable for MongoStorage {} 39 | impl CustomFunction for MongoStorage {} 40 | impl CustomFunctionMut for MongoStorage {} 41 | impl Index for MongoStorage {} 42 | impl IndexMut for MongoStorage {} 43 | impl Transaction for MongoStorage {} 44 | -------------------------------------------------------------------------------- /storages/mongo-storage/src/row.rs: -------------------------------------------------------------------------------- 1 | pub mod data_type; 2 | pub mod key; 3 | pub mod value; 4 | 5 | use { 6 | self::value::IntoValue, 7 | crate::error::ResultExt, 8 | gluesql_core::{ 9 | prelude::{DataType, Key, Result}, 10 | store::DataRow, 11 | }, 12 | mongodb::bson::Document, 13 | }; 14 | 15 | pub trait IntoRow { 16 | fn into_row<'a>( 17 | self, 18 | data_types: impl Iterator<Item = &'a DataType>, 19 | is_primary: bool, 20 | ) -> Result<(Key, DataRow)>; 21 | } 22 | 23 | impl IntoRow for Document { 24 | fn into_row<'a>( 25 | self, 26 | data_types: impl Iterator<Item = &'a DataType>, 27 | has_primary: bool, 28 | ) -> Result<(Key, DataRow)> { 29 | let key = match has_primary { 30 | true => self.get_binary_generic("_id").map_storage_err()?.to_owned(), 31 | false => self 32 | .get_object_id("_id") 33 | .map_storage_err()? 34 | .bytes() 35 | .to_vec(), 36 | }; 37 | let key = Key::Bytea(key); 38 | 39 | let row = self 40 | .into_iter() 41 | .skip(1) 42 | .zip(data_types) 43 | .map(|((_, bson), data_type)| bson.into_value(data_type).map_storage_err()) 44 | .collect::<Result<Vec<_>>>()?; 45 | 46 | Ok((key, DataRow::Vec(row))) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /storages/mongo-storage/src/row/key.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::error::MongoStorageError, 3 | bson::{Binary, Bson}, 4 | gluesql_core::prelude::Key, 5 | }; 6 | 7 | type Result<T> = std::result::Result<T, MongoStorageError>; 8 | 9 | pub trait KeyIntoBson { 10 | fn into_bson(self, has_primary: bool) -> Result<Bson>; 11 | } 12 | 13 | impl KeyIntoBson for Key { 14 | fn into_bson(self, has_primary: bool) -> Result<Bson> { 15 | match has_primary { 16 | true => Ok(Bson::Binary(Binary { 17 | subtype: bson::spec::BinarySubtype::Generic, 18 | bytes: self 19 | .to_cmp_be_bytes() 20 | .map_err(|_| MongoStorageError::UnsupportedBsonType)?, 21 | })), 22 | false => into_object_id(self), 23 | } 24 | } 25 | } 26 | 27 | pub fn into_object_id(key: Key) -> Result<Bson> { 28 | match key { 29 | Key::Bytea(bytes) => { 30 | let mut byte_array: [u8; 12] = [0; 12]; 31 | byte_array[..].copy_from_slice(&bytes[..]); 32 | 33 | Ok(Bson::ObjectId(bson::oid::ObjectId::from_bytes(byte_array))) 34 | } 35 | _ => Err(MongoStorageError::UnsupportedBsonType), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /storages/mongo-storage/tests/mongo_storage.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "test-mongo")] 2 | 3 | use { 4 | async_trait::async_trait, gluesql_core::prelude::Glue, gluesql_mongo_storage::MongoStorage, 5 | test_suite::*, 6 | }; 7 | 8 | struct MongoTester { 9 | glue: Glue<MongoStorage>, 10 | } 11 | 12 | #[async_trait(?Send)] 13 | impl Tester<MongoStorage> for MongoTester { 14 | async fn new(namespace: &str) -> Self { 15 | let conn_str = "mongodb://localhost:27017"; 16 | let storage = MongoStorage::new(conn_str, namespace) 17 | .await 18 | .expect("MongoStorage::new"); 19 | storage.drop_database().await.expect("database dropped"); 20 | let glue = Glue::new(storage); 21 | 22 | MongoTester { glue } 23 | } 24 | 25 | fn get_glue(&mut self) -> &mut Glue<MongoStorage> { 26 | &mut self.glue 27 | } 28 | } 29 | 30 | generate_store_tests!(tokio::test, MongoTester); 31 | generate_alter_table_tests!(tokio::test, MongoTester); 32 | -------------------------------------------------------------------------------- /storages/parquet-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-parquet-storage" 3 | authors = ["MeenSeek Kim <meenseek5929@naver.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | async-trait = "0.1" 14 | serde = { version = "1", features = ["derive"] } 15 | serde_json = "1.0.115" 16 | bincode = "1.3" 17 | futures = "0.3" 18 | tokio = { version = "1", features = ["rt", "macros"] } 19 | thiserror = "1.0" 20 | parquet = "43.0.0" 21 | byteorder = "1.4.3" 22 | num-traits = "0.2.5" 23 | rust_decimal = "1.15" 24 | lazy_static = "1.4.0" 25 | 26 | [dev-dependencies] 27 | test-suite.workspace = true 28 | uuid = "1" 29 | -------------------------------------------------------------------------------- /storages/parquet-storage/src/alter_table.rs: -------------------------------------------------------------------------------- 1 | use {super::ParquetStorage, gluesql_core::store::AlterTable}; 2 | 3 | impl AlterTable for ParquetStorage {} 4 | -------------------------------------------------------------------------------- /storages/parquet-storage/src/function.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::ParquetStorage, 3 | gluesql_core::store::{CustomFunction, CustomFunctionMut}, 4 | }; 5 | 6 | impl CustomFunctionMut for ParquetStorage {} 7 | impl CustomFunction for ParquetStorage {} 8 | -------------------------------------------------------------------------------- /storages/parquet-storage/src/index.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::ParquetStorage, 3 | gluesql_core::store::{Index, IndexMut}, 4 | }; 5 | 6 | impl Index for ParquetStorage {} 7 | impl IndexMut for ParquetStorage {} 8 | -------------------------------------------------------------------------------- /storages/parquet-storage/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use {super::ParquetStorage, gluesql_core::store::Transaction}; 2 | 3 | impl Transaction for ParquetStorage {} 4 | -------------------------------------------------------------------------------- /storages/parquet-storage/tests/error.rs: -------------------------------------------------------------------------------- 1 | use { 2 | gluesql_core::prelude::Glue, 3 | gluesql_parquet_storage::{ParquetStorage, error::ParquetStorageError}, 4 | }; 5 | 6 | #[tokio::test] 7 | async fn test_from_parquet_storage_error_to_error() { 8 | let path_str = "./tests/samples/"; 9 | let parquet_storage = ParquetStorage::new(path_str).unwrap(); 10 | let mut glue = Glue::new(parquet_storage); 11 | 12 | let cases = vec![( 13 | glue.execute("SELECT * FROM nested_maps_snappy").await, 14 | Err(ParquetStorageError::UnexpectedKeyTypeForMap("Int(1)".to_owned()).into()), 15 | )]; 16 | 17 | for (actual, expected) in cases { 18 | assert_eq!(actual.map(|mut payloads| payloads.remove(0)), expected); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /storages/parquet-storage/tests/parquet_storage.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, gluesql_core::prelude::Glue, gluesql_parquet_storage::ParquetStorage, 3 | std::fs::remove_dir_all, test_suite::*, 4 | }; 5 | 6 | struct ParquetTester { 7 | glue: Glue<ParquetStorage>, 8 | } 9 | 10 | #[async_trait(?Send)] 11 | impl Tester<ParquetStorage> for ParquetTester { 12 | async fn new(namespace: &str) -> Self { 13 | let path: String = format!("tmp/{namespace}"); 14 | 15 | if let Err(e) = remove_dir_all(&path) { 16 | println!("fs::remove_file {:?}", e); 17 | } 18 | let storage = ParquetStorage::new(&path).expect("ParquetStorage::new"); 19 | let glue = Glue::new(storage); 20 | 21 | ParquetTester { glue } 22 | } 23 | 24 | fn get_glue(&mut self) -> &mut Glue<ParquetStorage> { 25 | &mut self.glue 26 | } 27 | } 28 | 29 | generate_store_tests!(tokio::test, ParquetTester); 30 | generate_alter_table_tests!(tokio::test, ParquetTester); 31 | -------------------------------------------------------------------------------- /storages/parquet-storage/tests/samples/all_types_with_nulls.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/storages/parquet-storage/tests/samples/all_types_with_nulls.parquet -------------------------------------------------------------------------------- /storages/parquet-storage/tests/samples/alltypes_dictionary.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/storages/parquet-storage/tests/samples/alltypes_dictionary.parquet -------------------------------------------------------------------------------- /storages/parquet-storage/tests/samples/alltypes_plain.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/storages/parquet-storage/tests/samples/alltypes_plain.parquet -------------------------------------------------------------------------------- /storages/parquet-storage/tests/samples/alltypes_plain_snappy.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/storages/parquet-storage/tests/samples/alltypes_plain_snappy.parquet -------------------------------------------------------------------------------- /storages/parquet-storage/tests/samples/nested_lists_snappy.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/storages/parquet-storage/tests/samples/nested_lists_snappy.parquet -------------------------------------------------------------------------------- /storages/parquet-storage/tests/samples/nested_maps_snappy.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gluesql/gluesql/78b51bf019fc8c29a51182e464357bd76df35700/storages/parquet-storage/tests/samples/nested_maps_snappy.parquet -------------------------------------------------------------------------------- /storages/redb-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-redb-storage" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | redb = "2.6" 14 | async-trait = "0.1" 15 | serde = { version = "1", features = ["derive"] } 16 | futures = "0.3" 17 | thiserror = "2.0.3" 18 | bincode = "1.3.3" 19 | uuid = { version = "1.11.0", features = ["v7"] } 20 | async-stream = "0.3.6" 21 | 22 | [dev-dependencies] 23 | test-suite.workspace = true 24 | tokio = { version = "1", features = ["rt", "macros"] } 25 | -------------------------------------------------------------------------------- /storages/redb-storage/src/error.rs: -------------------------------------------------------------------------------- 1 | use {gluesql_core::error::Error, thiserror::Error as ThisError}; 2 | 3 | #[derive(ThisError, Debug)] 4 | pub enum StorageError { 5 | #[error("nested transaction is not supported")] 6 | NestedTransactionNotSupported, 7 | #[error("transaction not found")] 8 | TransactionNotFound, 9 | #[error("cannot create table with reserved name: {0}")] 10 | ReservedTableName(String), 11 | 12 | #[error(transparent)] 13 | Glue(#[from] Error), 14 | 15 | #[error(transparent)] 16 | RedbDatabase(#[from] redb::DatabaseError), 17 | #[error(transparent)] 18 | RedbStorage(#[from] redb::StorageError), 19 | #[error(transparent)] 20 | RedbTable(#[from] redb::TableError), 21 | #[error(transparent)] 22 | RedbTransaction(Box<redb::TransactionError>), 23 | #[error(transparent)] 24 | RedbCommit(#[from] redb::CommitError), 25 | 26 | #[error(transparent)] 27 | Bincode(#[from] bincode::Error), 28 | } 29 | 30 | impl From<StorageError> for Error { 31 | fn from(e: StorageError) -> Error { 32 | Error::StorageMsg(e.to_string()) 33 | } 34 | } 35 | 36 | impl From<redb::TransactionError> for StorageError { 37 | fn from(e: redb::TransactionError) -> StorageError { 38 | StorageError::RedbTransaction(Box::new(e)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /storages/redb-storage/tests/nested_transaction.rs: -------------------------------------------------------------------------------- 1 | use { 2 | gluesql_core::prelude::{Error, Glue}, 3 | gluesql_redb_storage::RedbStorage, 4 | }; 5 | 6 | #[tokio::test] 7 | async fn redb_nested_transaction() { 8 | let _ = std::fs::create_dir("tmp"); 9 | let path = "tmp/redb_nested_transaction"; 10 | let _ = std::fs::remove_file(path); 11 | 12 | let storage = RedbStorage::new(path).unwrap(); 13 | let mut glue = Glue::new(storage); 14 | 15 | glue.execute("BEGIN").await.unwrap(); 16 | let result = glue.execute("BEGIN").await; 17 | assert_eq!( 18 | result, 19 | Err(Error::StorageMsg( 20 | "nested transaction is not supported".to_owned() 21 | )) 22 | .map(|payload| vec![payload]) 23 | ); 24 | glue.execute("COMMIT;").await.unwrap(); 25 | } 26 | -------------------------------------------------------------------------------- /storages/redb-storage/tests/redb_storage.rs: -------------------------------------------------------------------------------- 1 | use { 2 | async_trait::async_trait, 3 | gluesql_core::prelude::Glue, 4 | gluesql_redb_storage::RedbStorage, 5 | std::fs::{create_dir, remove_file}, 6 | test_suite::*, 7 | }; 8 | 9 | struct RedbTester { 10 | glue: Glue<RedbStorage>, 11 | } 12 | 13 | #[async_trait(?Send)] 14 | impl Tester<RedbStorage> for RedbTester { 15 | async fn new(namespace: &str) -> Self { 16 | let _ = create_dir("tmp"); 17 | let path = format!("tmp/{}", namespace); 18 | let _ = remove_file(&path); 19 | 20 | let storage = RedbStorage::new(path).expect("[RedbTester] failed to create storage"); 21 | let glue = Glue::new(storage); 22 | 23 | Self { glue } 24 | } 25 | 26 | fn get_glue(&mut self) -> &mut Glue<RedbStorage> { 27 | &mut self.glue 28 | } 29 | } 30 | 31 | generate_store_tests!(tokio::test, RedbTester); 32 | generate_transaction_tests!(tokio::test, RedbTester); 33 | -------------------------------------------------------------------------------- /storages/redb-storage/tests/reserved_table_name.rs: -------------------------------------------------------------------------------- 1 | use { 2 | gluesql_core::prelude::{Error, Glue}, 3 | gluesql_redb_storage::RedbStorage, 4 | std::fs::{create_dir, remove_file}, 5 | }; 6 | 7 | #[tokio::test] 8 | async fn reserved_table_name() { 9 | let _ = create_dir("tmp"); 10 | let path = "tmp/redb_reserved_table_name"; 11 | let _ = remove_file(path); 12 | 13 | let storage = RedbStorage::new(path).unwrap(); 14 | let mut glue = Glue::new(storage); 15 | 16 | let result = glue.execute("CREATE TABLE __SCHEMA__ (id INTEGER);").await; 17 | 18 | assert_eq!( 19 | result, 20 | Err(Error::StorageMsg( 21 | "cannot create table with reserved name: __SCHEMA__".to_owned(), 22 | )) 23 | .map(|payload| vec![payload]) 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /storages/redb-storage/tests/storage_interface_error.rs: -------------------------------------------------------------------------------- 1 | use { 2 | gluesql_core::error::Error, 3 | gluesql_redb_storage::RedbStorage, 4 | std::fs::{create_dir, remove_file}, 5 | }; 6 | 7 | #[tokio::test] 8 | async fn redb_storage_interface_error() { 9 | let _ = create_dir("tmp"); 10 | let path = "tmp/redb_storage_interface_error"; 11 | let _ = remove_file(path); 12 | 13 | let storage1 = RedbStorage::new(path).expect("open first storage"); 14 | 15 | // Attempt to open the same database again using the storage interface 16 | let result = RedbStorage::new(path); 17 | let err = result.err().expect("second open should fail"); 18 | 19 | match err { 20 | Error::StorageMsg(msg) => assert!(msg.contains("Database already open")), 21 | other => panic!("unexpected error: {other:?}"), 22 | } 23 | 24 | drop(storage1); 25 | } 26 | -------------------------------------------------------------------------------- /storages/redis-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-redis-storage" 3 | authors = [ 4 | "Gioh Kim <gurugio@gmail.com>", 5 | "Taehoon Moon <taehoon.moon@outlook.com>", 6 | ] 7 | version.workspace = true 8 | edition.workspace = true 9 | description.workspace = true 10 | license.workspace = true 11 | repository.workspace = true 12 | documentation.workspace = true 13 | 14 | [dependencies] 15 | gluesql-core.workspace = true 16 | async-trait = "0.1" 17 | serde = { version = "1", features = ["derive"] } 18 | redis = "0.23.3" 19 | serde_json = "1.0.105" 20 | chrono = { version = "0.4.31", features = ["serde", "wasmbind"] } 21 | futures = "0.3" 22 | 23 | [dev-dependencies] 24 | test-suite.workspace = true 25 | tokio = { version = "1", features = ["rt", "macros"] } 26 | toml = "0.8.6" 27 | 28 | [features] 29 | test-redis = [] 30 | -------------------------------------------------------------------------------- /storages/redis-storage/src/index.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::RedisStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | ast::{IndexOperator, OrderByExpr}, 6 | data::Value, 7 | error::{Error, Result}, 8 | store::{Index, IndexMut, RowIter}, 9 | }, 10 | }; 11 | 12 | // Index is one of MUST-be-implemented traits. 13 | 14 | #[async_trait(?Send)] 15 | impl Index for RedisStorage { 16 | async fn scan_indexed_data<'a>( 17 | &'a self, 18 | _table_name: &str, 19 | _index_name: &str, 20 | _asc: Option<bool>, 21 | _cmp_value: Option<(&IndexOperator, Value)>, 22 | ) -> Result<RowIter<'a>> { 23 | Err(Error::StorageMsg( 24 | "[RedisStorage] index is not supported".to_owned(), 25 | )) 26 | } 27 | } 28 | 29 | #[async_trait(?Send)] 30 | impl IndexMut for RedisStorage { 31 | async fn create_index( 32 | &mut self, 33 | _table_name: &str, 34 | _index_name: &str, 35 | _column: &OrderByExpr, 36 | ) -> Result<()> { 37 | Err(Error::StorageMsg( 38 | "[RedisStorage] index is not supported".to_owned(), 39 | )) 40 | } 41 | 42 | async fn drop_index(&mut self, _table_name: &str, _index_name: &str) -> Result<()> { 43 | Err(Error::StorageMsg( 44 | "[RedisStorage] index is not supported".to_owned(), 45 | )) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /storages/redis-storage/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::RedisStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | error::{Error, Result}, 6 | store::Transaction, 7 | }, 8 | }; 9 | 10 | #[async_trait(?Send)] 11 | impl Transaction for RedisStorage { 12 | async fn begin(&mut self, autocommit: bool) -> Result<bool> { 13 | if autocommit { 14 | return Ok(false); 15 | } 16 | 17 | Err(Error::StorageMsg( 18 | "[RedisStorage] transaction is not supported".to_owned(), 19 | )) 20 | } 21 | 22 | async fn rollback(&mut self) -> Result<()> { 23 | Ok(()) 24 | } 25 | 26 | async fn commit(&mut self) -> Result<()> { 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /storages/redis-storage/tests/redis-storage.toml: -------------------------------------------------------------------------------- 1 | [redis] 2 | url="localhost" 3 | port=6379 4 | -------------------------------------------------------------------------------- /storages/shared-memory-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-shared-memory-storage" 3 | authors = [ 4 | "Jiseok CHOI <jiseok.dev@gmail.com>", 5 | "Taehoon Moon <taehoon.moon@outlook.com>", 6 | ] 7 | version.workspace = true 8 | edition.workspace = true 9 | description.workspace = true 10 | license.workspace = true 11 | repository.workspace = true 12 | documentation.workspace = true 13 | 14 | [dependencies] 15 | gluesql-core.workspace = true 16 | gluesql_memory_storage.workspace = true 17 | 18 | async-trait = "0.1" 19 | serde = { version = "1", features = ["derive"] } 20 | tokio = { version = "1", features = ["sync"] } 21 | futures = "0.3" 22 | 23 | [dev-dependencies] 24 | test-suite.workspace = true 25 | tokio = { version = "1", features = ["rt", "macros"] } 26 | -------------------------------------------------------------------------------- /storages/shared-memory-storage/src/index.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::SharedMemoryStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | ast::{IndexOperator, OrderByExpr}, 6 | data::Value, 7 | error::{Error, Result}, 8 | store::{Index, IndexMut, RowIter}, 9 | }, 10 | }; 11 | 12 | #[async_trait(?Send)] 13 | impl Index for SharedMemoryStorage { 14 | async fn scan_indexed_data<'a>( 15 | &'a self, 16 | _table_name: &str, 17 | _index_name: &str, 18 | _asc: Option<bool>, 19 | _cmp_value: Option<(&IndexOperator, Value)>, 20 | ) -> Result<RowIter<'a>> { 21 | Err(Error::StorageMsg( 22 | "[Shared MemoryStorage] index is not supported".to_owned(), 23 | )) 24 | } 25 | } 26 | 27 | #[async_trait(?Send)] 28 | impl IndexMut for SharedMemoryStorage { 29 | async fn create_index( 30 | &mut self, 31 | _table_name: &str, 32 | _index_name: &str, 33 | _column: &OrderByExpr, 34 | ) -> Result<()> { 35 | Err(Error::StorageMsg( 36 | "[Shared MemoryStorage] index is not supported".to_owned(), 37 | )) 38 | } 39 | 40 | async fn drop_index(&mut self, _table_name: &str, _index_name: &str) -> Result<()> { 41 | Err(Error::StorageMsg( 42 | "[Shared MemoryStorage] index is not supported".to_owned(), 43 | )) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /storages/shared-memory-storage/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use { 2 | super::SharedMemoryStorage, 3 | async_trait::async_trait, 4 | gluesql_core::{ 5 | error::{Error, Result}, 6 | store::Transaction, 7 | }, 8 | }; 9 | 10 | #[async_trait(?Send)] 11 | impl Transaction for SharedMemoryStorage { 12 | async fn begin(&mut self, autocommit: bool) -> Result<bool> { 13 | if autocommit { 14 | return Ok(false); 15 | } 16 | 17 | Err(Error::StorageMsg( 18 | "[Shared MemoryStorage] transaction is not supported".to_owned(), 19 | )) 20 | } 21 | 22 | async fn rollback(&mut self) -> Result<()> { 23 | Err(Error::StorageMsg( 24 | "[Shared MemoryStorage] transaction is not supported".to_owned(), 25 | )) 26 | } 27 | 28 | async fn commit(&mut self) -> Result<()> { 29 | Err(Error::StorageMsg( 30 | "[Shared MemoryStorage] transaction is not supported".to_owned(), 31 | )) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /storages/sled-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql_sled_storage" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | utils.workspace = true 14 | async-trait = "0.1" 15 | iter-enum = "1" 16 | serde = { version = "1", features = ["derive"] } 17 | thiserror = "1" 18 | bincode = "1" 19 | sled = "0.34" 20 | async-io = "1" 21 | futures = "0.3" 22 | 23 | [dev-dependencies] 24 | test-suite.workspace = true 25 | criterion = "0.3" 26 | tokio = { version = "1", features = ["rt", "macros"] } 27 | 28 | [[bench]] 29 | name = "sled_benchmark" 30 | harness = false 31 | -------------------------------------------------------------------------------- /storages/web-storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-web-storage" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | async-trait = "0.1" 14 | serde = { version = "1", features = ["derive"] } 15 | gloo-storage = "0.2.2" 16 | uuid = { version = "1.2.2", features = ["v4", "v7"] } 17 | web-sys = { version = "0.3.60" } 18 | futures = "0.3" 19 | 20 | [dev-dependencies] 21 | test-suite.workspace = true 22 | wasm-bindgen-test = "0.3.33" 23 | -------------------------------------------------------------------------------- /storages/web-storage/README.md: -------------------------------------------------------------------------------- 1 | ## 🚴 WebStorage 2 | * localStorage 3 | * sessionStorage 4 | 5 | ### 🔬 Test in Headless Browsers with `wasm-pack test` 6 | ``` 7 | wasm-pack test --headless --firefox --chrome 8 | ``` 9 | -------------------------------------------------------------------------------- /storages/web-storage/tests/local_storage.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "wasm32")] 2 | 3 | use { 4 | async_trait::async_trait, 5 | gluesql_core::prelude::Glue, 6 | gluesql_web_storage::{WebStorage, WebStorageType}, 7 | test_suite::*, 8 | wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}, 9 | }; 10 | 11 | wasm_bindgen_test_configure!(run_in_browser); 12 | 13 | struct LocalStorageTester { 14 | glue: Glue<WebStorage>, 15 | } 16 | 17 | #[async_trait(?Send)] 18 | impl Tester<WebStorage> for LocalStorageTester { 19 | async fn new(_: &str) -> Self { 20 | let storage = WebStorage::new(WebStorageType::Local); 21 | storage.raw().clear().unwrap(); 22 | 23 | let glue = Glue::new(storage); 24 | 25 | LocalStorageTester { glue } 26 | } 27 | 28 | fn get_glue(&mut self) -> &mut Glue<WebStorage> { 29 | &mut self.glue 30 | } 31 | } 32 | 33 | generate_store_tests!(wasm_bindgen_test, LocalStorageTester); 34 | generate_alter_table_tests!(wasm_bindgen_test, LocalStorageTester); 35 | -------------------------------------------------------------------------------- /storages/web-storage/tests/session_storage.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "wasm32")] 2 | 3 | use { 4 | async_trait::async_trait, 5 | gluesql_core::prelude::Glue, 6 | gluesql_web_storage::{WebStorage, WebStorageType}, 7 | test_suite::*, 8 | wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}, 9 | }; 10 | 11 | wasm_bindgen_test_configure!(run_in_browser); 12 | 13 | struct SessionStorageTester { 14 | glue: Glue<WebStorage>, 15 | } 16 | 17 | #[async_trait(?Send)] 18 | impl Tester<WebStorage> for SessionStorageTester { 19 | async fn new(_: &str) -> Self { 20 | let storage = WebStorage::new(WebStorageType::Session); 21 | storage.raw().clear().unwrap(); 22 | 23 | let glue = Glue::new(storage); 24 | 25 | SessionStorageTester { glue } 26 | } 27 | 28 | fn get_glue(&mut self) -> &mut Glue<WebStorage> { 29 | &mut self.glue 30 | } 31 | } 32 | 33 | generate_store_tests!(wasm_bindgen_test, SessionStorageTester); 34 | generate_alter_table_tests!(wasm_bindgen_test, SessionStorageTester); 35 | -------------------------------------------------------------------------------- /test-suite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-test-suite" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | gluesql-core.workspace = true 13 | async-trait = "0.1" 14 | bigdecimal = "0.4.2" 15 | chrono = "0.4.31" 16 | rust_decimal = "1" 17 | hex = "0.4" 18 | serde_json = "1.0.91" 19 | pretty_assertions = "1" 20 | 21 | [target.'cfg(target_arch = "wasm32")'.dependencies.uuid] 22 | version = "1" 23 | features = ["v4", "v7", "js"] 24 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies.uuid] 25 | version = "1" 26 | features = ["v4", "v7"] 27 | -------------------------------------------------------------------------------- /test-suite/src/aggregate.rs: -------------------------------------------------------------------------------- 1 | pub mod avg; 2 | pub mod count; 3 | pub mod error; 4 | pub mod group_by; 5 | pub mod max; 6 | pub mod min; 7 | pub mod stdev; 8 | pub mod sum; 9 | pub mod variance; 10 | -------------------------------------------------------------------------------- /test-suite/src/aggregate/avg.rs: -------------------------------------------------------------------------------- 1 | use {crate::*, gluesql_core::prelude::Value::*}; 2 | 3 | test_case!(avg, { 4 | let g = get_tester!(); 5 | 6 | g.run( 7 | " 8 | CREATE TABLE Item ( 9 | id INTEGER, 10 | quantity INTEGER, 11 | age INTEGER NULL, 12 | total INTEGER 13 | ); 14 | ", 15 | ) 16 | .await; 17 | g.run( 18 | " 19 | INSERT INTO Item (id, quantity, age, total) VALUES 20 | (1, 10, 11, 1), 21 | (2, 0, 90, 2), 22 | (3, 9, NULL, 3), 23 | (4, 3, 3, 1), 24 | (5, 25, NULL, 1); 25 | ", 26 | ) 27 | .await; 28 | 29 | let test_cases = [ 30 | ( 31 | "SELECT AVG(age) FROM Item", 32 | select_with_null!("AVG(age)"; Null), 33 | ), 34 | ( 35 | "SELECT AVG(id), AVG(quantity) FROM Item", 36 | select!( 37 | "AVG(id)" | "AVG(quantity)" 38 | F64 | F64; 39 | 3.0 9.4 40 | ), 41 | ), 42 | ]; 43 | 44 | for (sql, expected) in test_cases { 45 | g.test(sql, Ok(expected)).await; 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /test-suite/src/aggregate/error.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::error::{EvaluateError, TranslateError}, 4 | }; 5 | 6 | test_case!(error, { 7 | let g = get_tester!(); 8 | 9 | g.run( 10 | " 11 | CREATE TABLE Item ( 12 | id INTEGER, 13 | quantity INTEGER, 14 | age INTEGER NULL, 15 | total INTEGER 16 | ); 17 | ", 18 | ) 19 | .await; 20 | g.run( 21 | " 22 | INSERT INTO Item (id, quantity, age, total) VALUES 23 | (1, 10, 11, 1), 24 | (2, 0, 90, 2), 25 | (3, 9, NULL, 3), 26 | (4, 3, 3, 1), 27 | (5, 25, NULL, 1); 28 | ", 29 | ) 30 | .await; 31 | 32 | let test_cases = [ 33 | ( 34 | "SELECT SUM(num) FROM Item;", 35 | EvaluateError::IdentifierNotFound("num".to_owned()).into(), 36 | ), 37 | ( 38 | "SELECT COUNT(Foo.*) FROM Item;", 39 | TranslateError::QualifiedWildcardInCountNotSupported("Foo.*".to_owned()).into(), 40 | ), 41 | ( 42 | "SELECT SUM(*) FROM Item;", 43 | TranslateError::WildcardFunctionArgNotAccepted.into(), 44 | ), 45 | ]; 46 | 47 | for (sql, error) in test_cases { 48 | g.test(sql, Err(error)).await; 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /test-suite/src/aggregate/stdev.rs: -------------------------------------------------------------------------------- 1 | use {crate::*, gluesql_core::prelude::Value::*}; 2 | 3 | test_case!(stdev, { 4 | let g = get_tester!(); 5 | 6 | g.run( 7 | " 8 | CREATE TABLE Item ( 9 | id INTEGER, 10 | quantity INTEGER, 11 | age INTEGER NULL, 12 | total INTEGER 13 | ); 14 | ", 15 | ) 16 | .await; 17 | g.run( 18 | " 19 | INSERT INTO Item (id, quantity, age, total) VALUES 20 | (1, 10, 11, 1), 21 | (2, 0, 90, 2), 22 | (3, 9, NULL, 3), 23 | (4, 3, 3, 1), 24 | (5, 25, NULL, 1); 25 | ", 26 | ) 27 | .await; 28 | 29 | let test_cases = [ 30 | ( 31 | "SELECT STDEV(age) FROM Item", 32 | select_with_null!("STDEV(age)"; Null), 33 | ), 34 | ( 35 | "SELECT STDEV(total) FROM Item", 36 | select!( 37 | "STDEV(total)" 38 | F64; 39 | 0.8 40 | ), 41 | ), 42 | ]; 43 | 44 | for (sql, expected) in test_cases { 45 | g.test(sql, Ok(expected)).await; 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /test-suite/src/aggregate/variance.rs: -------------------------------------------------------------------------------- 1 | use {crate::*, gluesql_core::prelude::Value::*}; 2 | 3 | test_case!(variance, { 4 | let g = get_tester!(); 5 | 6 | g.run( 7 | " 8 | CREATE TABLE Item ( 9 | id INTEGER, 10 | quantity INTEGER, 11 | age INTEGER NULL, 12 | total INTEGER 13 | ); 14 | ", 15 | ) 16 | .await; 17 | g.run( 18 | " 19 | INSERT INTO Item (id, quantity, age, total) VALUES 20 | (1, 10, 11, 1), 21 | (2, 0, 90, 2), 22 | (3, 9, NULL, 3), 23 | (4, 3, 3, 1), 24 | (5, 25, NULL, 1); 25 | ", 26 | ) 27 | .await; 28 | 29 | let test_cases = [ 30 | ( 31 | "SELECT VARIANCE(age) FROM Item", 32 | select_with_null!("VARIANCE(age)"; Null), 33 | ), 34 | ( 35 | "SELECT VARIANCE(id), VARIANCE(quantity) FROM Item", 36 | select!( 37 | "VARIANCE(id)" | "VARIANCE(quantity)" 38 | F64 | F64; 39 | 2.0 74.64 40 | ), 41 | ), 42 | ]; 43 | 44 | for (sql, expected) in test_cases { 45 | g.test(sql, Ok(expected)).await; 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /test-suite/src/alter.rs: -------------------------------------------------------------------------------- 1 | mod alter_table; 2 | mod create_table; 3 | mod drop_indexed; 4 | mod drop_table; 5 | 6 | pub use { 7 | alter_table::{alter_table_add_drop, alter_table_rename}, 8 | create_table::create_table, 9 | drop_indexed::{drop_indexed_column, drop_indexed_table}, 10 | drop_table::drop_table, 11 | }; 12 | -------------------------------------------------------------------------------- /test-suite/src/arithmetic.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod on_where; 3 | pub mod project; 4 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder.rs: -------------------------------------------------------------------------------- 1 | pub mod alias_as; 2 | pub mod basic; 3 | pub mod delete; 4 | pub mod expr; 5 | pub mod function; 6 | pub mod index_by; 7 | pub mod insert; 8 | pub mod schemaless; 9 | pub mod select; 10 | pub mod statements; 11 | pub mod update; 12 | pub mod values; 13 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/expr.rs: -------------------------------------------------------------------------------- 1 | pub mod pattern_matching; 2 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/function.rs: -------------------------------------------------------------------------------- 1 | pub mod datetime; 2 | pub mod math; 3 | pub mod other; 4 | pub mod text; 5 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/function/datetime.rs: -------------------------------------------------------------------------------- 1 | mod conversion; 2 | mod current_date_and_time; 3 | mod formatting; 4 | 5 | pub use { 6 | conversion::conversion, current_date_and_time::current_date_and_time, formatting::formatting, 7 | }; 8 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/function/math.rs: -------------------------------------------------------------------------------- 1 | mod basic_arithmetic; 2 | mod conversion; 3 | mod rounding; 4 | pub use {basic_arithmetic::basic_arithmetic, conversion::conversion, rounding::rounding}; 5 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/function/other.rs: -------------------------------------------------------------------------------- 1 | pub mod coalesce; 2 | pub mod ifnull; 3 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/function/text.rs: -------------------------------------------------------------------------------- 1 | mod trimming; 2 | pub use trimming::trimming; 3 | 4 | mod case_conversion; 5 | pub use case_conversion::case_conversion; 6 | 7 | mod character_conversion; 8 | pub use character_conversion::character_conversion; 9 | 10 | mod padding; 11 | pub use padding::padding; 12 | 13 | mod position_and_indexing; 14 | pub use position_and_indexing::position_and_indexing; 15 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/index_by.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::{ 4 | ast_builder::*, 5 | prelude::{Payload, Value::*}, 6 | }, 7 | }; 8 | 9 | test_case!(index_by, { 10 | let glue = get_glue!(); 11 | 12 | let actual = table("Foo") 13 | .create_table() 14 | .add_column("id INTEGER PRIMARY KEY") 15 | .add_column("name TEXT") 16 | .execute(glue) 17 | .await; 18 | let expected = Ok(Payload::Create); 19 | assert_eq!(actual, expected, "create table - Foo"); 20 | 21 | let actual = table("Foo") 22 | .insert() 23 | .columns("id, name") 24 | .values(vec![vec![num(1), text("Drink")]]) 25 | .execute(glue) 26 | .await; 27 | let expected = Ok(Payload::Insert(1)); 28 | assert_eq!(actual, expected, "insert - specifying columns"); 29 | 30 | let actual = table("Foo") 31 | .index_by(primary_key().eq("1")) 32 | .select() 33 | .project("id, name") 34 | .execute(glue) 35 | .await; 36 | let expected = Ok(select!( 37 | id | name 38 | I64 | Str; 39 | 1 "Drink".to_owned() 40 | )); 41 | assert_eq!(actual, expected, "basic select with index by"); 42 | }); 43 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/schemaless.rs: -------------------------------------------------------------------------------- 1 | pub mod basic; 2 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/statements.rs: -------------------------------------------------------------------------------- 1 | pub mod querying; 2 | -------------------------------------------------------------------------------- /test-suite/src/ast_builder/statements/querying.rs: -------------------------------------------------------------------------------- 1 | pub mod data_aggregation; 2 | pub use data_aggregation::data_aggregation; 3 | 4 | pub mod data_selection_and_projection; 5 | pub use data_selection_and_projection::data_selection_and_projection; 6 | -------------------------------------------------------------------------------- /test-suite/src/data_type.rs: -------------------------------------------------------------------------------- 1 | pub mod bytea; 2 | pub mod date; 3 | pub mod decimal; 4 | pub mod float32; 5 | pub mod inet; 6 | pub mod int128; 7 | pub mod int16; 8 | pub mod int32; 9 | pub mod int64; 10 | pub mod int8; 11 | pub mod interval; 12 | pub mod list; 13 | pub mod map; 14 | pub mod point; 15 | pub mod sql_types; 16 | pub mod time; 17 | pub mod timestamp; 18 | pub mod uint128; 19 | pub mod uint16; 20 | pub mod uint32; 21 | pub mod uint64; 22 | pub mod uint8; 23 | pub mod uuid; 24 | -------------------------------------------------------------------------------- /test-suite/src/function.rs: -------------------------------------------------------------------------------- 1 | pub mod abs; 2 | pub mod add_month; 3 | pub mod append; 4 | pub mod ascii; 5 | pub mod cast; 6 | pub mod ceil; 7 | pub mod chr; 8 | pub mod coalesce; 9 | pub mod concat; 10 | pub mod concat_ws; 11 | pub mod dedup; 12 | pub mod degrees; 13 | pub mod div_mod; 14 | pub mod entries; 15 | pub mod exp_log; 16 | pub mod extract; 17 | pub mod find_idx; 18 | pub mod floor; 19 | pub mod format; 20 | pub mod gcd_lcm; 21 | pub mod generate_uuid; 22 | pub mod geometry; 23 | pub mod greatest; 24 | pub mod ifnull; 25 | pub mod initcap; 26 | pub mod is_empty; 27 | pub mod keys; 28 | pub mod last_day; 29 | pub mod left_right; 30 | pub mod length; 31 | pub mod lpad_rpad; 32 | pub mod ltrim_rtrim; 33 | pub mod math_function; 34 | pub mod md5; 35 | pub mod now; 36 | pub mod pi; 37 | pub mod position; 38 | pub mod prepend; 39 | pub mod radians; 40 | pub mod rand; 41 | pub mod repeat; 42 | pub mod replace; 43 | pub mod reverse; 44 | pub mod round; 45 | pub mod sign; 46 | pub mod skip; 47 | pub mod slice; 48 | pub mod sort; 49 | pub mod splice; 50 | pub mod sqrt_power; 51 | pub mod substr; 52 | pub mod take; 53 | pub mod to_date; 54 | pub mod trim; 55 | pub mod upper_lower; 56 | pub mod values; 57 | -------------------------------------------------------------------------------- /test-suite/src/function/dedup.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::{executor::EvaluateError, prelude::Value::*}, 4 | }; 5 | 6 | test_case!(dedup, { 7 | let g = get_tester!(); 8 | 9 | g.named_test( 10 | "DEDUP(CAST('[1, 2, 3, 3, 4, 5, 5]' AS List)) should return '[1, 2, 3, 4, 5]'", 11 | "SELECT DEDUP(CAST('[1, 2, 3, 3, 4, 5, 5]' AS List)) as actual", 12 | Ok(select!(actual List; vec![I64(1), I64(2), I64(3), I64(4), I64(5)])), 13 | ) 14 | .await; 15 | 16 | g.named_test( 17 | "DEDUP(CAST('['1', 1, '1']' AS List)) should return '['1', 1]'", 18 | r#"SELECT DEDUP(CAST('["1", 1, 1, "1", "1"]' AS List)) as actual"#, 19 | Ok(select!(actual List; vec![Str("1".to_owned()), I64(1), Str("1".to_owned())])), 20 | ) 21 | .await; 22 | 23 | g.named_test( 24 | "DEDUP with invalid value should return EvaluateError::ListTypeRequired", 25 | "SELECT DEDUP(1) AS actual", 26 | Err(EvaluateError::ListTypeRequired.into()), 27 | ) 28 | .await; 29 | }); 30 | -------------------------------------------------------------------------------- /test-suite/src/function/entries.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::{ 4 | error::EvaluateError, 5 | prelude::{Payload, Value::*}, 6 | }, 7 | }; 8 | 9 | test_case!(entries, { 10 | let g = get_tester!(); 11 | 12 | g.named_test( 13 | "test entries function works while creating a table simultaneously", 14 | "CREATE TABLE Item (map MAP)", 15 | Ok(Payload::Create), 16 | ) 17 | .await; 18 | g.named_test( 19 | "test if the sample string gets inserted to table", 20 | r#"INSERT INTO Item VALUES ('{"name":"GlueSQL"}')"#, 21 | Ok(Payload::Insert(1)), 22 | ) 23 | .await; 24 | g.named_test( 25 | "check id the entries function works with the previously inserted string", 26 | "SELECT ENTRIES(map) AS test FROM Item", 27 | Ok(select!( 28 | "test"; 29 | List; 30 | vec![ 31 | List(vec![Str("name".to_owned()), Str("GlueSQL".to_owned())]) 32 | ] 33 | )), 34 | ) 35 | .await; 36 | g.named_test( 37 | "test ENTRIES function requires map value", 38 | "SELECT ENTRIES(1) FROM Item", 39 | Err(EvaluateError::FunctionRequiresMapValue("ENTRIES".to_owned()).into()), 40 | ) 41 | .await; 42 | }); 43 | -------------------------------------------------------------------------------- /test-suite/src/function/generate_uuid.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::{ast::DataType, error::TranslateError}, 4 | }; 5 | 6 | test_case!(generate_uuid, { 7 | let g = get_tester!(); 8 | 9 | let test_cases = [( 10 | "SELECT generate_uuid(0) as uuid", 11 | Err(TranslateError::FunctionArgsLengthNotMatching { 12 | name: "GENERATE_UUID".to_owned(), 13 | expected: 0, 14 | found: 1, 15 | } 16 | .into()), 17 | )]; 18 | 19 | for (sql, expected) in test_cases { 20 | g.test(sql, expected).await; 21 | } 22 | 23 | g.count("SELECT GENERATE_UUID()", 1).await; 24 | g.count("VALUES (GENERATE_UUID())", 1).await; 25 | g.type_match("SELECT GENERATE_UUID() as uuid", &[DataType::Uuid]) 26 | .await; 27 | g.type_match("VALUES (GENERATE_UUID())", &[DataType::Uuid]) 28 | .await; 29 | }); 30 | -------------------------------------------------------------------------------- /test-suite/src/function/geometry.rs: -------------------------------------------------------------------------------- 1 | mod calc_distance; 2 | mod get_x; 3 | mod get_y; 4 | 5 | pub use {calc_distance::calc_distance, get_x::get_x, get_y::get_y}; 6 | -------------------------------------------------------------------------------- /test-suite/src/function/md5.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::{error::TranslateError, prelude::Value::*}, 4 | }; 5 | 6 | test_case!(md5, { 7 | let g = get_tester!(); 8 | 9 | g.test( 10 | "VALUES(MD5('GlueSQL'))", 11 | Ok(select!( 12 | column1 13 | Str; 14 | "4274ecec96f3ee59b51b168dc6137231".to_owned() 15 | )), 16 | ) 17 | .await; 18 | 19 | g.test( 20 | "VALUES(MD5('GlueSQL Hi'))", 21 | Ok(select!( 22 | column1 23 | Str; 24 | "eab30259ac1a92b66794f301a6ac3ff3".to_owned() 25 | )), 26 | ) 27 | .await; 28 | 29 | g.test(r#"VALUES(MD5(NULL))"#, Ok(select_with_null!(column1; Null))) 30 | .await; 31 | 32 | g.test( 33 | r#"VALUES(MD5())"#, 34 | Err(TranslateError::FunctionArgsLengthNotMatching { 35 | name: "MD5".to_owned(), 36 | expected: 1, 37 | found: 0, 38 | } 39 | .into()), 40 | ) 41 | .await; 42 | }); 43 | -------------------------------------------------------------------------------- /test-suite/src/function/now.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::prelude::{Payload, Value::*}, 4 | }; 5 | 6 | test_case!(now, { 7 | let g = get_tester!(); 8 | 9 | macro_rules! t { 10 | ($timestamp: expr) => { 11 | $timestamp.parse().unwrap() 12 | }; 13 | } 14 | 15 | let test_cases = [ 16 | ( 17 | "CREATE TABLE Item (time TIMESTAMP DEFAULT NOW())", 18 | Ok(Payload::Create), 19 | ), 20 | ( 21 | "INSERT INTO Item VALUES 22 | ('2021-10-13T06:42:40.364832862'), 23 | ('9999-12-31T23:59:40.364832862');", 24 | Ok(Payload::Insert(2)), 25 | ), 26 | ( 27 | "SELECT time FROM Item WHERE time > NOW();", 28 | Ok(select!("time" Timestamp; t!("9999-12-31T23:59:40.364832862"))), 29 | ), 30 | ]; 31 | 32 | for (sql, expected) in test_cases { 33 | g.test(sql, expected).await; 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /test-suite/src/function/pi.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::{error::TranslateError, prelude::Value::*}, 4 | }; 5 | 6 | test_case!(pi, { 7 | let g = get_tester!(); 8 | 9 | let test_cases = [ 10 | ( 11 | "SELECT PI() AS pi", 12 | Ok(select!( 13 | pi 14 | F64; 15 | std::f64::consts::PI 16 | )), 17 | ), 18 | ( 19 | "SELECT PI(0) AS pi", 20 | Err(TranslateError::FunctionArgsLengthNotMatching { 21 | name: "PI".to_owned(), 22 | expected: 0, 23 | found: 1, 24 | } 25 | .into()), 26 | ), 27 | ]; 28 | 29 | for (sql, expected) in test_cases { 30 | g.test(sql, expected).await; 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /test-suite/src/function/position.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::*, 3 | gluesql_core::{ 4 | error::ValueError, 5 | prelude::{ 6 | Payload, 7 | Value::{self, *}, 8 | }, 9 | }, 10 | }; 11 | 12 | test_case!(position, { 13 | let g = get_tester!(); 14 | 15 | let test_cases = [ 16 | ("CREATE TABLE Food (name Text null)", Ok(Payload::Create)), 17 | ("INSERT INTO Food VALUES ('pork')", Ok(Payload::Insert(1))), 18 | ("INSERT INTO Food VALUES ('burger')", Ok(Payload::Insert(1))), 19 | ( 20 | "SELECT POSITION('e' IN name) AS test FROM Food", 21 | Ok(select!(test; I64; 0; 5)), 22 | ), 23 | ( 24 | "SELECT POSITION('s' IN 'cheese') AS test", 25 | Ok(select!(test; I64; 5)), 26 | ), 27 | ( 28 | "SELECT POSITION(NULL IN 'cheese') AS test", 29 | Ok(select_with_null!(test; Null)), 30 | ), 31 | ( 32 | "SELECT POSITION(1 IN 'cheese') AS test", 33 | Err(ValueError::NonStringParameterInPosition { 34 | from: Value::Str("cheese".to_owned()), 35 | sub: Value::I64(1), 36 | } 37 | .into()), 38 | ), 39 | ]; 40 | for (sql, expected) in test_cases { 41 | g.test(sql, expected).await; 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /test-suite/src/index.rs: -------------------------------------------------------------------------------- 1 | mod and; 2 | mod basic; 3 | mod expr; 4 | mod nested; 5 | mod null; 6 | mod order_by; 7 | mod showindexes; 8 | mod value; 9 | 10 | pub use { 11 | and::and, 12 | basic::basic, 13 | expr::expr, 14 | nested::nested, 15 | null::null, 16 | order_by::{order_by, order_by_multi}, 17 | showindexes::showindexes, 18 | value::value, 19 | }; 20 | -------------------------------------------------------------------------------- /test-suite/src/metadata.rs: -------------------------------------------------------------------------------- 1 | pub mod index; 2 | pub mod table; 3 | -------------------------------------------------------------------------------- /test-suite/src/metadata/index.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{concat_with, row, select, stringify_label, test_case}, 3 | gluesql_core::prelude::{Payload, Value::Str}, 4 | }; 5 | 6 | test_case!(index, { 7 | let g = get_tester!(); 8 | 9 | let cases = vec![ 10 | ("CREATE TABLE Meta (id INT, name TEXT)", Ok(Payload::Create)), 11 | ( 12 | "CREATE INDEX Meta_id ON Meta (id)", 13 | Ok(Payload::CreateIndex), 14 | ), 15 | ( 16 | "CREATE INDEX Meta_name ON Meta (name)", 17 | Ok(Payload::CreateIndex), 18 | ), 19 | ( 20 | "SELECT OBJECT_NAME, OBJECT_TYPE FROM GLUE_OBJECTS", 21 | Ok(select!( 22 | OBJECT_NAME | OBJECT_TYPE ; 23 | Str | Str ; 24 | "Meta".to_owned() "TABLE".to_owned(); 25 | "Meta_id".to_owned() "INDEX".to_owned(); 26 | "Meta_name".to_owned() "INDEX".to_owned() 27 | )), 28 | ), 29 | ]; 30 | 31 | for (actual, expected) in cases { 32 | g.test(actual, expected).await; 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /test-suite/src/metadata/table.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::{row, select, stringify_label, test_case}, 3 | gluesql_core::prelude::{Payload, Value::Str}, 4 | }; 5 | 6 | test_case!(table, { 7 | let g = get_tester!(); 8 | 9 | let cases = vec![ 10 | ("CREATE TABLE Meta (id INT, name TEXT)", Ok(Payload::Create)), 11 | ( 12 | "SELECT OBJECT_NAME, OBJECT_TYPE 13 | FROM GLUE_OBJECTS 14 | WHERE CREATED > NOW() - INTERVAL 1 MINUTE", 15 | Ok(select!( 16 | OBJECT_NAME | OBJECT_TYPE ; 17 | Str | Str ; 18 | "Meta".to_owned() "TABLE".to_owned() 19 | )), 20 | ), 21 | ("DROP TABLE Meta", Ok(Payload::DropTable(1))), 22 | ( 23 | "SELECT COUNT(*) 24 | FROM GLUE_OBJECTS 25 | WHERE CREATED > NOW() - INTERVAL 1 MINUTE", 26 | Ok(Payload::Select { 27 | labels: vec!["COUNT(*)".to_owned()], 28 | rows: Vec::new(), 29 | }), 30 | ), 31 | ]; 32 | 33 | for (actual, expected) in cases { 34 | g.test(actual, expected).await; 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /test-suite/src/schemaless.rs: -------------------------------------------------------------------------------- 1 | mod basic; 2 | mod error; 3 | 4 | pub use {basic::basic, error::error}; 5 | -------------------------------------------------------------------------------- /test-suite/src/store.rs: -------------------------------------------------------------------------------- 1 | pub mod insert_schema; 2 | -------------------------------------------------------------------------------- /test-suite/src/transaction.rs: -------------------------------------------------------------------------------- 1 | mod alter_table; 2 | mod ast_builder; 3 | mod basic; 4 | mod dictionary; 5 | mod index; 6 | mod table; 7 | 8 | pub use { 9 | alter_table::*, ast_builder::*, basic::basic, dictionary::dictionary, index::*, table::*, 10 | }; 11 | -------------------------------------------------------------------------------- /test-suite/src/transaction/dictionary.rs: -------------------------------------------------------------------------------- 1 | use {crate::*, gluesql_core::prelude::*}; 2 | 3 | test_case!(dictionary, { 4 | let g = get_tester!(); 5 | 6 | let tables = |v: Vec<&str>| { 7 | Ok(Payload::ShowVariable(PayloadVariable::Tables( 8 | v.into_iter().map(ToOwned::to_owned).collect(), 9 | ))) 10 | }; 11 | 12 | g.run("CREATE TABLE Garlic (id INTEGER);").await; 13 | g.test("SHOW TABLES;", tables(vec!["Garlic"])).await; 14 | 15 | g.run("BEGIN;").await; 16 | g.test("SHOW TABLES;", tables(vec!["Garlic"])).await; 17 | 18 | g.run("CREATE TABLE Noodle (id INTEGER);").await; 19 | g.test("SHOW TABLES;", tables(vec!["Garlic", "Noodle"])) 20 | .await; 21 | 22 | g.run("ROLLBACK;").await; 23 | g.test("SHOW TABLES;", tables(vec!["Garlic"])).await; 24 | 25 | g.run("BEGIN;").await; 26 | g.run("CREATE TABLE Apple (id INTEGER);").await; 27 | g.run("CREATE TABLE Rice (id INTEGER);").await; 28 | g.test("SHOW TABLES;", tables(vec!["Apple", "Garlic", "Rice"])) 29 | .await; 30 | 31 | g.run("COMMIT;").await; 32 | g.test("SHOW TABLES;", tables(vec!["Apple", "Garlic", "Rice"])) 33 | .await; 34 | }); 35 | -------------------------------------------------------------------------------- /test-suite/src/type_match.rs: -------------------------------------------------------------------------------- 1 | use {crate::*, gluesql_core::ast::DataType}; 2 | 3 | test_case!(type_match, { 4 | let g = get_tester!(); 5 | 6 | g.run("CREATE TABLE TypeMatch (uuid_value UUID, float_value FLOAT, int_value INT, bool_value BOOLEAN)").await; 7 | g.run("INSERT INTO TypeMatch values(GENERATE_UUID(), 1.0, 1, true)") 8 | .await; 9 | g.type_match( 10 | "SELECT * FROM TypeMatch", 11 | &[ 12 | DataType::Uuid, 13 | DataType::Float, 14 | DataType::Int, 15 | DataType::Boolean, 16 | ], 17 | ) 18 | .await; 19 | }); 20 | -------------------------------------------------------------------------------- /test-suite/src/validate.rs: -------------------------------------------------------------------------------- 1 | pub mod types; 2 | pub mod unique; 3 | -------------------------------------------------------------------------------- /utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gluesql-utils" 3 | authors = ["Taehoon Moon <taehoon.moon@outlook.com>"] 4 | version.workspace = true 5 | edition.workspace = true 6 | description.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | 11 | [dependencies] 12 | indexmap = "1" 13 | pin-project = "1" 14 | futures = "0.3" 15 | -------------------------------------------------------------------------------- /utils/src/hashmap.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, hash::Hash}; 2 | 3 | pub trait HashMapExt<K, V, I> { 4 | fn concat(self, entries: I) -> Self; 5 | } 6 | 7 | impl<K, V, I> HashMapExt<K, V, I> for HashMap<K, V> 8 | where 9 | K: Hash + Eq, 10 | I: Iterator<Item = (K, V)>, 11 | { 12 | fn concat(mut self, entries: I) -> Self { 13 | for (key, value) in entries { 14 | self.insert(key, value); 15 | } 16 | 17 | self 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use {super::HashMapExt, std::collections::HashMap}; 24 | 25 | #[test] 26 | fn concat() { 27 | let values: HashMap<&str, i64> = [("a", 10), ("b", 20)].into(); 28 | let new_items = [("c", 30), ("d", 40), ("e", 50)]; 29 | 30 | let actual = values.concat(new_items.into_iter()); 31 | let expected = [("a", 10), ("b", 20), ("c", 30), ("d", 40), ("e", 50)].into(); 32 | 33 | assert_eq!(actual, expected); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /utils/src/indexmap.rs: -------------------------------------------------------------------------------- 1 | use { 2 | indexmap::map::{IntoIter, Keys}, 3 | std::{cmp::Eq, hash::Hash}, 4 | }; 5 | 6 | /// HashMap which provides 7 | /// 1. Immutable APIs 8 | /// 2. Preserving insertion order 9 | pub struct IndexMap<K, V>(indexmap::IndexMap<K, V>); 10 | 11 | impl<K: Hash + Eq, V> IndexMap<K, V> { 12 | pub fn new() -> Self { 13 | Self(indexmap::IndexMap::new()) 14 | } 15 | 16 | pub fn insert(mut self, key: K, value: V) -> (Self, Option<V>) { 17 | let existing = self.0.insert(key, value); 18 | 19 | (self, existing) 20 | } 21 | 22 | pub fn get(&self, key: &K) -> Option<&V> { 23 | self.0.get(key) 24 | } 25 | 26 | pub fn keys(&self) -> Keys<K, V> { 27 | self.0.keys() 28 | } 29 | 30 | pub fn len(&self) -> usize { 31 | self.0.len() 32 | } 33 | 34 | pub fn is_empty(&self) -> bool { 35 | self.0.is_empty() 36 | } 37 | } 38 | 39 | impl<K: Hash + Eq, V> Default for IndexMap<K, V> { 40 | fn default() -> Self { 41 | Self::new() 42 | } 43 | } 44 | 45 | impl<K: Hash + Eq, V> IntoIterator for IndexMap<K, V> { 46 | type Item = (K, V); 47 | type IntoIter = IntoIter<K, V>; 48 | 49 | fn into_iter(self) -> Self::IntoIter { 50 | self.0.into_iter() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::str_to_string)] 2 | 3 | mod hashmap; 4 | mod indexmap; 5 | mod or_stream; 6 | mod vector; 7 | 8 | pub use {self::indexmap::IndexMap, hashmap::HashMapExt, or_stream::OrStream, vector::Vector}; 9 | --------------------------------------------------------------------------------