sqlty 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlty-0.1.0 → sqlty-0.2.0}/PKG-INFO +8 -6
- {sqlty-0.1.0 → sqlty-0.2.0}/README.md +7 -5
- {sqlty-0.1.0 → sqlty-0.2.0}/pyproject.toml +2 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/Cargo.lock +4 -4
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/Cargo.toml +1 -1
- sqlty-0.2.0/rust/crates/sqlty-ext/build.rs +51 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-ext/src/lib.rs +13 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/checker.rs +187 -9
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/fixer.rs +3 -2
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/rules.rs +11 -2
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/tokens.rs +13 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/build.rs +3 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/README.md +96 -18
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/common/aggregates.inference.toml +41 -1
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/common/conditional.inference.toml +17 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/common/datetime.inference.toml +25 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/common/json.inference.toml +34 -2
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/common/math.inference.toml +17 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/common/strings.inference.toml +33 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/common/window.inference.toml +41 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/compile.rs +387 -69
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/dialects.toml +6 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/aggregates.inference.toml +49 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/arithmetic.inference.toml +132 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/bits.inference.toml +10 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/comparison.inference.toml +28 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/conditional.inference.toml +19 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/datetime.inference.toml +136 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/encryption.inference.toml +46 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/json.inference.toml +102 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/math.inference.toml +84 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/misc.inference.toml +109 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/spatial.inference.toml +120 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/strings.inference.toml +228 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/mysql/system.inference.toml +118 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/admin.inference.toml +145 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/aggregates.inference.toml +178 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/postgres/arithmetic.inference.toml +185 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/arrays.inference.toml +120 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/binary.inference.toml +46 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/comparison.inference.toml +20 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/postgres/concat.inference.toml +20 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/conditional.inference.toml +10 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/datetime.inference.toml +127 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/enums.inference.toml +19 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/events.inference.toml +19 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/extensions.inference.toml +28 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/formatting.inference.toml +28 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/geometry.inference.toml +175 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/json.inference.toml +258 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/math.inference.toml +92 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/merge.inference.toml +10 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/network.inference.toml +64 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/ranges.inference.toml +136 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/rows.inference.toml +10 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/sequences.inference.toml +10 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/series.inference.toml +19 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/strings.inference.toml +101 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/system.inference.toml +353 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/textsearch.inference.toml +162 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/uuid.inference.toml +28 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/postgres/xml.inference.toml +55 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/aggregates.inference.toml +29 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/arithmetic.inference.toml +54 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/bitwise.inference.toml +17 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/datetime.inference.toml +28 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/fts.inference.toml +37 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/json.inference.toml +100 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/math.inference.toml +38 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/scalar.inference.toml +91 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/rules/sqlite/strings.inference.toml +10 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/api.rs +7 -0
- sqlty-0.2.0/rust/crates/sqlty-sql/src/analyzer/dml.rs +745 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/explain.rs +6 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/expr.rs +184 -73
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/functions.rs +137 -493
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/grouping.rs +1 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/inference.rs +95 -12
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/json.rs +1 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/literals.rs +7 -1
- sqlty-0.2.0/rust/crates/sqlty-sql/src/analyzer/merged_star.rs +178 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/mod.rs +18 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/names.rs +4 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/narrowing.rs +98 -8
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/nullability.rs +404 -115
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/operators.rs +162 -4
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/params.rs +66 -1
- sqlty-0.2.0/rust/crates/sqlty-sql/src/analyzer/pg_record_functions.rs +599 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/preprocess.rs +97 -13
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/query.rs +156 -25
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/references.rs +21 -1
- sqlty-0.2.0/rust/crates/sqlty-sql/src/analyzer/registry.rs +264 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/resolve.rs +45 -18
- sqlty-0.2.0/rust/crates/sqlty-sql/src/analyzer/respell.rs +557 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/scope.rs +85 -17
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__dml__tests__returning_columns_in_queries.snap +11 -10
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__expr__tests__arrays_and_subscripts.snap +1 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__expr__tests__composite_fields_and_json_access.snap +1 -2
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__expr__tests__literals_and_unary_operators.snap +2 -2
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__expr__tests__misc_expressions.snap +1 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__expr__tests__mysql_and_sqlite_expressions.snap +5 -6
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__functions__tests__function_availability_differs_by_dialect.snap +1 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__operators__tests__division_differs_between_postgres_and_mysql.snap +1 -2
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__dialects_name_and_type_the_same_query_differently.snap +3 -3
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__unsupported_query_forms_are_explained.snap +3 -2
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__values_lists.snap +23 -4
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__quoted_names_keep_their_case_in_postgres_only.snap +1 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__star_over_unreadable_sources_names_the_reason.snap +8 -4
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__scope__tests__opaque_sources_explain_themselves.snap +5 -5
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__scope__tests__star_expansion_over_joins.snap +15 -6
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/sql_type.rs +5 -4
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/table_functions.rs +103 -32
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/tests.rs +2 -2
- sqlty-0.2.0/rust/crates/sqlty-sql/src/analyzer/text_names.rs +174 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/catalog/alter.rs +62 -6
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/catalog/columns.rs +25 -4
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/catalog/loader.rs +208 -20
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/catalog/mod.rs +73 -6
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/catalog/read.rs +157 -8
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/catalog/resolve.rs +25 -7
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/ddl.rs +325 -20
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/diagnostics.rs +6 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/tests/inference_compiler.rs +180 -0
- sqlty-0.2.0/skills/sqlty/SKILL.md +81 -0
- sqlty-0.2.0/skills/sqlty-adopt/SKILL.md +125 -0
- sqlty-0.2.0/skills/sqlty-breaking/SKILL.md +130 -0
- sqlty-0.2.0/skills/sqlty-check/SKILL.md +144 -0
- sqlty-0.2.0/skills/sqlty-ci/SKILL.md +120 -0
- sqlty-0.2.0/skills/sqlty-rewrite/SKILL.md +134 -0
- sqlty-0.2.0/src/sqlty/_breaking.py +314 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_check.py +32 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_cli.py +69 -24
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_core.pyi +2 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_drivers.py +16 -16
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_helpers.py +17 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_lint.py +15 -1
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_params.py +39 -6
- sqlty-0.2.0/src/sqlty/_snapshot.py +169 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_source.py +4 -4
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_type_extractor/ast_fields.py +55 -9
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_type_extractor/extract.py +59 -10
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/core.py +34 -29
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/insert-missing-column.md +9 -4
- sqlty-0.2.0/src/sqlty/rules/insert-value-count.md +58 -0
- sqlty-0.2.0/src/sqlty/rules/narrow-type.md +50 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/param-type-mismatch.md +6 -2
- sqlty-0.2.0/src/sqlty/rules/stale-type.md +56 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/unparsed-query.md +2 -2
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/unsupported-syntax.md +3 -3
- sqlty-0.1.0/rust/crates/sqlty-sql/rules/mysql/aggregates.inference.toml +0 -21
- sqlty-0.1.0/rust/crates/sqlty-sql/rules/mysql/arithmetic.inference.toml +0 -30
- sqlty-0.1.0/rust/crates/sqlty-sql/rules/mysql/comparison.inference.toml +0 -10
- sqlty-0.1.0/rust/crates/sqlty-sql/rules/mysql/json.inference.toml +0 -21
- sqlty-0.1.0/rust/crates/sqlty-sql/rules/mysql/strings.inference.toml +0 -51
- sqlty-0.1.0/rust/crates/sqlty-sql/rules/postgres/aggregates.inference.toml +0 -51
- sqlty-0.1.0/rust/crates/sqlty-sql/rules/postgres/json.inference.toml +0 -123
- sqlty-0.1.0/rust/crates/sqlty-sql/rules/sqlite/json.inference.toml +0 -28
- sqlty-0.1.0/rust/crates/sqlty-sql/src/analyzer/dialect_functions.rs +0 -379
- sqlty-0.1.0/rust/crates/sqlty-sql/src/analyzer/dml.rs +0 -388
- sqlty-0.1.0/rust/crates/sqlty-sql/src/analyzer/registry.rs +0 -439
- sqlty-0.1.0/src/sqlty/_breaking.py +0 -191
- sqlty-0.1.0/src/sqlty/_widening.py +0 -89
- sqlty-0.1.0/src/sqlty/rules/stale-type.md +0 -50
- sqlty-0.1.0/src/sqlty/skill/SKILL.md +0 -110
- {sqlty-0.1.0 → sqlty-0.2.0}/LICENSE +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-ext/Cargo.toml +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/Cargo.toml +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/edit.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/lib.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/options.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/rewriter.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/scan.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-python/src/template.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/Cargo.toml +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/common/arithmetic.inference.toml +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/common/comparison.inference.toml +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/common/concat.inference.toml +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/rules/mysql/concat.inference.toml +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/dialect.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/hashing.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/memo.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__expr__tests__case_expressions.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__expr__tests__casts.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__expr__tests__casts_without_a_schema_do_not_complain.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__functions__tests__aggregates_in_postgres.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__functions__tests__return_types_follow_the_argument.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__functions__tests__unknown_functions_are_untyped_and_explained.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__literals__tests__value_sets_flow_through_expressions.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__names__tests__column_names_follow_postgres_figure_colname.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__operators__tests__arithmetic_promotes_numbers_and_types_date_math.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__operators__tests__binary_operators_in_queries.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__operators__tests__common_type_follows_postgres_union_rules.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__column_names_come_from_the_first_branch.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__ctes.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__projection_forms.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__recursive_ctes_settle_nullability.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__set_operations_combine_nullability.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__set_operations_unify_types.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__query__tests__statements_that_are_not_queries_have_no_columns.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__ambiguous_names_are_reported_once_per_query.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__cte_columns_are_resolved_like_tables.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__names_are_not_judged_without_a_schema.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__nullability_comes_from_the_declaration.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__system_columns_exist_without_being_declared.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__unknown_names_say_what_is_missing.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__resolve__tests__unqualified_names_resolve_to_the_first_source_that_has_them.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__scope__tests__correlated_subqueries_see_the_outer_scope.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__scope__tests__derived_tables_ctes_and_views.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__scope__tests__nullability_follows_the_join_kind.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/snapshots/sqlty_sql__analyzer__scope__tests__table_names_fold_by_dialect.snap +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/star_expand.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/test_support.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/analyzer/typed.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/catalog/builtin_catalog.sql +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/catalog/names.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/json.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/lib.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/placeholders.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/src/values.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/rust/crates/sqlty-sql/tests/timing.rs +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/__init__.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/__main__.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_config.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_decode.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_gitignore.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_json_shape.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_json_types.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_psycopg.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_type_extractor/__init__.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_type_extractor/compat.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_type_extractor/generic.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_type_extractor/info.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_type_extractor/sandbox.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/_type_extractor/subtype.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/drivers/psycopg.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/magic.py +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/py.typed +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/ambiguous-column.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/column-count-mismatch.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/column-name-mismatch.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/column-type-mismatch.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/fstring-skipped.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/inline-sql-comment.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/json-shape-mismatch.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/literal-mismatch.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/mysql-sql-marker.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/nullable-mismatch.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/param-count-mismatch.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/redundant-literal.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/redundant-optional.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/select-star-no-schema.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/select-star.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/stale-rewrite.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/tstring-skipped.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/typeddict-inheritance.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/typeddict-key-mismatch.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/unchecked-query.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/ungrouped-column.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/unknown-column.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/unknown-function.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/unknown-table.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/unknown-type.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/untyped-query.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty/rules/unused-ignore.md +0 -0
- {sqlty-0.1.0 → sqlty-0.2.0}/src/sqlty_magic.pth +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlty
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -111,13 +111,14 @@ CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT);
|
|
|
111
111
|
```python
|
|
112
112
|
from sqlty import SQL
|
|
113
113
|
|
|
114
|
-
# error: [
|
|
114
|
+
# error: [narrow-type] "SQL[tuple[int, str]] should be SQL[tuple[int, str | None]]"
|
|
115
115
|
query = SQL[tuple[int, str]]("--sql\nSELECT id, email FROM users")
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
`sqlty check --fix .` updates the type, and your type checker finds every
|
|
119
|
-
caller that now needs a `None` check. `
|
|
120
|
-
|
|
119
|
+
caller that now needs a `None` check. `narrow-type` is an error, so CI fails on
|
|
120
|
+
it; an annotation that is only wider than the rows, such as `str | None` for a
|
|
121
|
+
column made `NOT NULL`, is a `stale-type` warning
|
|
121
122
|
([CI](https://jirikuncar.github.io/sqlty/recipes/ci/)). For rolling deploys,
|
|
122
123
|
`sqlty breaking --against origin/main` exits 1 when old code breaks on the new
|
|
123
124
|
schema or new code on the old one
|
|
@@ -174,8 +175,9 @@ conn.execute("--sql\nSELECT emial FROM users")
|
|
|
174
175
|
asyncpg calls ([Parameters](https://jirikuncar.github.io/sqlty/guide/parameters/)).
|
|
175
176
|
These are the mistakes coding agents make: every problem is one
|
|
176
177
|
`file:line: [rule] message` line and a non-zero exit code they can act on, and
|
|
177
|
-
the [sqlty
|
|
178
|
-
them the conventions.
|
|
178
|
+
the [sqlty skills](https://jirikuncar.github.io/sqlty/recipes/agents/) teach
|
|
179
|
+
them the conventions. In Claude Code, `/plugin install sqlty --marketplace jirikuncar/sqlty`
|
|
180
|
+
installs them.
|
|
179
181
|
|
|
180
182
|
## Get started
|
|
181
183
|
|
|
@@ -78,13 +78,14 @@ CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT);
|
|
|
78
78
|
```python
|
|
79
79
|
from sqlty import SQL
|
|
80
80
|
|
|
81
|
-
# error: [
|
|
81
|
+
# error: [narrow-type] "SQL[tuple[int, str]] should be SQL[tuple[int, str | None]]"
|
|
82
82
|
query = SQL[tuple[int, str]]("--sql\nSELECT id, email FROM users")
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
`sqlty check --fix .` updates the type, and your type checker finds every
|
|
86
|
-
caller that now needs a `None` check. `
|
|
87
|
-
|
|
86
|
+
caller that now needs a `None` check. `narrow-type` is an error, so CI fails on
|
|
87
|
+
it; an annotation that is only wider than the rows, such as `str | None` for a
|
|
88
|
+
column made `NOT NULL`, is a `stale-type` warning
|
|
88
89
|
([CI](https://jirikuncar.github.io/sqlty/recipes/ci/)). For rolling deploys,
|
|
89
90
|
`sqlty breaking --against origin/main` exits 1 when old code breaks on the new
|
|
90
91
|
schema or new code on the old one
|
|
@@ -141,8 +142,9 @@ conn.execute("--sql\nSELECT emial FROM users")
|
|
|
141
142
|
asyncpg calls ([Parameters](https://jirikuncar.github.io/sqlty/guide/parameters/)).
|
|
142
143
|
These are the mistakes coding agents make: every problem is one
|
|
143
144
|
`file:line: [rule] message` line and a non-zero exit code they can act on, and
|
|
144
|
-
the [sqlty
|
|
145
|
-
them the conventions.
|
|
145
|
+
the [sqlty skills](https://jirikuncar.github.io/sqlty/recipes/agents/) teach
|
|
146
|
+
them the conventions. In Claude Code, `/plugin install sqlty --marketplace jirikuncar/sqlty`
|
|
147
|
+
installs them.
|
|
146
148
|
|
|
147
149
|
## Get started
|
|
148
150
|
|
|
@@ -61,7 +61,8 @@ manifest-path = "rust/crates/sqlty-ext/Cargo.toml"
|
|
|
61
61
|
# registers the lazy "sqlty" codec lookup (see docs/guide/magic-codec.md).
|
|
62
62
|
# The path is relative to the project root (maturin >= 1.12.1): the wheel gets
|
|
63
63
|
# it at the root (python-source prefix stripped), the sdist at src/.
|
|
64
|
-
|
|
64
|
+
# The Agent Skills are compiled into the extension, so only the sdist needs them.
|
|
65
|
+
include = ["src/sqlty_magic.pth", { path = "skills/**/*", format = "sdist" }]
|
|
65
66
|
|
|
66
67
|
[dependency-groups]
|
|
67
68
|
dev = [
|
|
@@ -614,7 +614,7 @@ dependencies = [
|
|
|
614
614
|
|
|
615
615
|
[[package]]
|
|
616
616
|
name = "sqlty-bench"
|
|
617
|
-
version = "0.
|
|
617
|
+
version = "0.2.0"
|
|
618
618
|
dependencies = [
|
|
619
619
|
"criterion",
|
|
620
620
|
"sqlty-python",
|
|
@@ -623,7 +623,7 @@ dependencies = [
|
|
|
623
623
|
|
|
624
624
|
[[package]]
|
|
625
625
|
name = "sqlty-core"
|
|
626
|
-
version = "0.
|
|
626
|
+
version = "0.2.0"
|
|
627
627
|
dependencies = [
|
|
628
628
|
"pyo3",
|
|
629
629
|
"sqlty-python",
|
|
@@ -633,7 +633,7 @@ dependencies = [
|
|
|
633
633
|
|
|
634
634
|
[[package]]
|
|
635
635
|
name = "sqlty-python"
|
|
636
|
-
version = "0.
|
|
636
|
+
version = "0.2.0"
|
|
637
637
|
dependencies = [
|
|
638
638
|
"regex",
|
|
639
639
|
"sqlty-sql",
|
|
@@ -642,7 +642,7 @@ dependencies = [
|
|
|
642
642
|
|
|
643
643
|
[[package]]
|
|
644
644
|
name = "sqlty-sql"
|
|
645
|
-
version = "0.
|
|
645
|
+
version = "0.2.0"
|
|
646
646
|
dependencies = [
|
|
647
647
|
"insta",
|
|
648
648
|
"regex",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//! Embeds the Agent Skills of `skills/` at the repository root, so `sqlty skill` prints the
|
|
2
|
+
//! ones of its own version wherever the wheel is installed.
|
|
3
|
+
|
|
4
|
+
use std::fmt::Write as _;
|
|
5
|
+
use std::path::{Path, PathBuf};
|
|
6
|
+
use std::{env, fs};
|
|
7
|
+
|
|
8
|
+
fn main() {
|
|
9
|
+
let root = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("../../../skills");
|
|
10
|
+
let root = root
|
|
11
|
+
.canonicalize()
|
|
12
|
+
.unwrap_or_else(|e| panic!("{}: {e}", root.display()));
|
|
13
|
+
let mut files = Vec::new();
|
|
14
|
+
collect(&root, &mut files);
|
|
15
|
+
files.sort();
|
|
16
|
+
let mut out = String::from("pub(crate) static SKILLS: &[(&str, &str)] = &[\n");
|
|
17
|
+
for file in &files {
|
|
18
|
+
let relative = file
|
|
19
|
+
.strip_prefix(&root)
|
|
20
|
+
.unwrap()
|
|
21
|
+
.to_str()
|
|
22
|
+
.unwrap()
|
|
23
|
+
.replace('\\', "/");
|
|
24
|
+
writeln!(
|
|
25
|
+
out,
|
|
26
|
+
" ({relative:?}, include_str!({:?})),",
|
|
27
|
+
file.to_str().unwrap()
|
|
28
|
+
)
|
|
29
|
+
.unwrap();
|
|
30
|
+
}
|
|
31
|
+
out.push_str("];\n");
|
|
32
|
+
fs::write(
|
|
33
|
+
Path::new(&env::var("OUT_DIR").unwrap()).join("skills.rs"),
|
|
34
|
+
out,
|
|
35
|
+
)
|
|
36
|
+
.unwrap();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Every file under `dir`, and every directory for Cargo to watch.
|
|
40
|
+
fn collect(dir: &Path, files: &mut Vec<PathBuf>) {
|
|
41
|
+
println!("cargo:rerun-if-changed={}", dir.display());
|
|
42
|
+
for entry in fs::read_dir(dir).unwrap() {
|
|
43
|
+
let path = entry.unwrap().path();
|
|
44
|
+
if path.is_dir() {
|
|
45
|
+
collect(&path, files);
|
|
46
|
+
} else {
|
|
47
|
+
println!("cargo:rerun-if-changed={}", path.display());
|
|
48
|
+
files.push(path);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -52,6 +52,7 @@ type ParamTuple = (
|
|
|
52
52
|
Option<String>,
|
|
53
53
|
Option<bool>,
|
|
54
54
|
bool,
|
|
55
|
+
Option<String>,
|
|
55
56
|
);
|
|
56
57
|
|
|
57
58
|
/// A parsed schema and the tables and views it could not parse.
|
|
@@ -296,6 +297,14 @@ fn infer_type(
|
|
|
296
297
|
Ok(py.detach(|| analyzer::infer_type(sql, dialect, schema(schema_sql, dialect).as_deref()))?)
|
|
297
298
|
}
|
|
298
299
|
|
|
300
|
+
include!(concat!(env!("OUT_DIR"), "/skills.rs"));
|
|
301
|
+
|
|
302
|
+
/// The files of the Agent Skills, as `(path, text)` with paths like `sqlty/SKILL.md`.
|
|
303
|
+
#[pyfunction]
|
|
304
|
+
fn skills() -> Vec<(&'static str, &'static str)> {
|
|
305
|
+
SKILLS.to_vec()
|
|
306
|
+
}
|
|
307
|
+
|
|
299
308
|
/// Result columns of a query as `(name, python_type, sql_type)` tuples.
|
|
300
309
|
///
|
|
301
310
|
/// Returns None when the query returns no rows or doesn't parse; unknown types are None.
|
|
@@ -453,6 +462,7 @@ fn analyze_params(
|
|
|
453
462
|
param.sql_type,
|
|
454
463
|
nullable(param.nullability),
|
|
455
464
|
param.cast,
|
|
465
|
+
param.untyped_sql_type,
|
|
456
466
|
)
|
|
457
467
|
})
|
|
458
468
|
.collect(),
|
|
@@ -483,7 +493,8 @@ fn name_rules() -> Vec<&'static str> {
|
|
|
483
493
|
|
|
484
494
|
/// Why a query has untyped columns, or names what it cannot, as `(rule, message)` pairs:
|
|
485
495
|
/// `unknown-table`, `unknown-column`, `ambiguous-column`, `unknown-function`,
|
|
486
|
-
/// `unknown-type`, `unsupported-syntax`, `insert-missing-column` and
|
|
496
|
+
/// `unknown-type`, `unsupported-syntax`, `insert-missing-column`, `insert-value-count` and
|
|
497
|
+
/// `ungrouped-column`.
|
|
487
498
|
/// Tables, columns and
|
|
488
499
|
/// ambiguity are only judged with a schema.
|
|
489
500
|
///
|
|
@@ -821,6 +832,7 @@ fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
821
832
|
m.add_function(wrap_pyfunction!(check_source, m)?)?;
|
|
822
833
|
m.add_function(wrap_pyfunction!(wrapped_queries, m)?)?;
|
|
823
834
|
m.add_function(wrap_pyfunction!(query_sites, m)?)?;
|
|
835
|
+
m.add_function(wrap_pyfunction!(skills, m)?)?;
|
|
824
836
|
Ok(())
|
|
825
837
|
}
|
|
826
838
|
|
|
@@ -410,7 +410,7 @@ impl Checker<'_> {
|
|
|
410
410
|
|
|
411
411
|
/// `untyped-query`, `stale-type` and the literal rules, against the inferred row type.
|
|
412
412
|
fn annotation(&mut self, call: &Call, sql_content: &str, line: usize) {
|
|
413
|
-
let annotated =
|
|
413
|
+
let annotated = call.row_type(self.scan.source());
|
|
414
414
|
let wrapper = if call.template {
|
|
415
415
|
self.options.style.template_name()
|
|
416
416
|
} else {
|
|
@@ -437,10 +437,28 @@ impl Checker<'_> {
|
|
|
437
437
|
return;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
if is_scalar(annotated) && self.rules.is_enabled(Rule::NarrowType) {
|
|
441
|
+
if let Some(current) = self
|
|
442
|
+
.infer(sql_content)
|
|
443
|
+
.map(|current| self.spelling.respell(current))
|
|
444
|
+
.filter(|current| current.starts_with("tuple["))
|
|
445
|
+
{
|
|
446
|
+
let wrapper = if call.template { "SQLTemplate" } else { "SQL" };
|
|
447
|
+
self.report_fix(
|
|
448
|
+
Rule::NarrowType,
|
|
449
|
+
format!("{wrapper}[{annotated}] should be {wrapper}[{current}]: a row is never a single value"),
|
|
450
|
+
line,
|
|
451
|
+
vec![Edit::new(call.annotation.clone(), current)],
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
440
456
|
let literals = self.rules.is_enabled(Rule::LiteralMismatch)
|
|
441
457
|
|| self.rules.is_enabled(Rule::RedundantLiteral);
|
|
442
458
|
if !(annotated.starts_with("tuple[")
|
|
443
|
-
&& (self.rules.is_enabled(Rule::StaleType)
|
|
459
|
+
&& (self.rules.is_enabled(Rule::StaleType)
|
|
460
|
+
|| self.rules.is_enabled(Rule::NarrowType)
|
|
461
|
+
|| literals))
|
|
444
462
|
{
|
|
445
463
|
return;
|
|
446
464
|
}
|
|
@@ -448,9 +466,14 @@ impl Checker<'_> {
|
|
|
448
466
|
.infer(sql_content)
|
|
449
467
|
.map(|current| self.spelling.respell(current))
|
|
450
468
|
{
|
|
451
|
-
|
|
469
|
+
let spelled = spell_unions(annotated);
|
|
470
|
+
if !refines_tuple(&spelled, ¤t) {
|
|
452
471
|
self.report_fix(
|
|
453
|
-
|
|
472
|
+
if admits_tuple(&spelled, ¤t) {
|
|
473
|
+
Rule::StaleType
|
|
474
|
+
} else {
|
|
475
|
+
Rule::NarrowType
|
|
476
|
+
},
|
|
454
477
|
format!(
|
|
455
478
|
"{}[{annotated}] should be {}[{current}]",
|
|
456
479
|
if call.template { "SQLTemplate" } else { "SQL" },
|
|
@@ -460,7 +483,7 @@ impl Checker<'_> {
|
|
|
460
483
|
vec![Edit::new(call.annotation.clone(), current.clone())],
|
|
461
484
|
);
|
|
462
485
|
}
|
|
463
|
-
for (rule, message) in literal_findings(
|
|
486
|
+
for (rule, message) in literal_findings(&spelled, ¤t) {
|
|
464
487
|
self.report(rule, message, line);
|
|
465
488
|
}
|
|
466
489
|
}
|
|
@@ -683,13 +706,39 @@ pub fn wrapped_queries(source: &str, style: &ImportStyle) -> Vec<(usize, String,
|
|
|
683
706
|
});
|
|
684
707
|
(
|
|
685
708
|
scan.lines.line_at(call.span.start),
|
|
686
|
-
|
|
709
|
+
call.row_type(source).to_string(),
|
|
687
710
|
query,
|
|
688
711
|
)
|
|
689
712
|
})
|
|
690
713
|
.collect()
|
|
691
714
|
}
|
|
692
715
|
|
|
716
|
+
/// Whether `annotated` is a builtin scalar or collection, such as `int` or `list[str]`:
|
|
717
|
+
/// a driver returns each row as a tuple, a dict or an instance of a row class.
|
|
718
|
+
fn is_scalar(annotated: &str) -> bool {
|
|
719
|
+
let members = split_top(annotated, '|');
|
|
720
|
+
members.iter().any(|member| member.trim() != "None")
|
|
721
|
+
&& members.iter().all(|member| {
|
|
722
|
+
let base = member.trim().split('[').next().unwrap_or_default();
|
|
723
|
+
matches!(
|
|
724
|
+
base.strip_prefix("typing.").unwrap_or(base),
|
|
725
|
+
"None"
|
|
726
|
+
| "int"
|
|
727
|
+
| "str"
|
|
728
|
+
| "float"
|
|
729
|
+
| "bool"
|
|
730
|
+
| "bytes"
|
|
731
|
+
| "complex"
|
|
732
|
+
| "list"
|
|
733
|
+
| "set"
|
|
734
|
+
| "frozenset"
|
|
735
|
+
| "List"
|
|
736
|
+
| "Set"
|
|
737
|
+
| "FrozenSet"
|
|
738
|
+
)
|
|
739
|
+
})
|
|
740
|
+
}
|
|
741
|
+
|
|
693
742
|
/// The items of a `tuple[...]` type, split at its top-level commas.
|
|
694
743
|
fn tuple_items(ty: &str) -> Option<Vec<&str>> {
|
|
695
744
|
let inner = ty.strip_prefix("tuple[")?.strip_suffix(']')?;
|
|
@@ -755,6 +804,69 @@ fn split_top(text: &str, sep: char) -> Vec<&str> {
|
|
|
755
804
|
parts
|
|
756
805
|
}
|
|
757
806
|
|
|
807
|
+
/// The members of a tuple item, with `Optional[X]`, `Union[A, B]` and a quoted annotation
|
|
808
|
+
/// spelled out: `Optional[str]` and `"str | None"` are `["str", "None"]`.
|
|
809
|
+
fn union_members(item: &str) -> Vec<&str> {
|
|
810
|
+
let parts = split_top(item, '|');
|
|
811
|
+
if parts.len() > 1 {
|
|
812
|
+
return parts.into_iter().flat_map(union_members).collect();
|
|
813
|
+
}
|
|
814
|
+
let item = item.trim();
|
|
815
|
+
let quoted = ['"', '\''].iter().find_map(|"e| {
|
|
816
|
+
item.strip_prefix(quote)
|
|
817
|
+
.and_then(|rest| rest.strip_suffix(quote))
|
|
818
|
+
});
|
|
819
|
+
if let Some(inner) = quoted {
|
|
820
|
+
return union_members(inner);
|
|
821
|
+
}
|
|
822
|
+
let bare = item.strip_prefix("typing.").unwrap_or(item);
|
|
823
|
+
if let Some(inner) = bare
|
|
824
|
+
.strip_prefix("Optional[")
|
|
825
|
+
.and_then(|rest| rest.strip_suffix(']'))
|
|
826
|
+
{
|
|
827
|
+
let mut members = union_members(inner);
|
|
828
|
+
members.push("None");
|
|
829
|
+
return members;
|
|
830
|
+
}
|
|
831
|
+
if let Some(inner) = bare
|
|
832
|
+
.strip_prefix("Union[")
|
|
833
|
+
.and_then(|rest| rest.strip_suffix(']'))
|
|
834
|
+
{
|
|
835
|
+
return split_top(inner, ',')
|
|
836
|
+
.into_iter()
|
|
837
|
+
.flat_map(union_members)
|
|
838
|
+
.collect();
|
|
839
|
+
}
|
|
840
|
+
vec![item]
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/// `annotated` with each `Optional[X]`, `Union[A, B]` or quoted item spelled `A | B | None`,
|
|
844
|
+
/// as sqlty writes it.
|
|
845
|
+
fn spell_unions(annotated: &str) -> String {
|
|
846
|
+
let Some(items) = tuple_items(annotated) else {
|
|
847
|
+
return annotated.to_string();
|
|
848
|
+
};
|
|
849
|
+
let items: Vec<String> = items
|
|
850
|
+
.iter()
|
|
851
|
+
.map(|item| {
|
|
852
|
+
let members = union_members(item);
|
|
853
|
+
let mut spelled: Vec<&str> = members.iter().copied().filter(|m| *m != "None").collect();
|
|
854
|
+
if spelled.len() < members.len() {
|
|
855
|
+
spelled.push("None");
|
|
856
|
+
}
|
|
857
|
+
spelled.join(" | ")
|
|
858
|
+
})
|
|
859
|
+
.collect();
|
|
860
|
+
format!("tuple[{}]", items.join(", "))
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/// Whether two tuple items are the same union, in any order and spelling.
|
|
864
|
+
fn same_union(annotated: &str, inferred: &str) -> bool {
|
|
865
|
+
let (ours, theirs) = (union_members(annotated), union_members(inferred));
|
|
866
|
+
ours.iter().all(|member| theirs.contains(member))
|
|
867
|
+
&& theirs.iter().all(|member| ours.contains(member))
|
|
868
|
+
}
|
|
869
|
+
|
|
758
870
|
fn literal_member(text: &str) -> Option<Lit> {
|
|
759
871
|
let Some(quote) = text.chars().next().filter(|c| matches!(c, '\'' | '"')) else {
|
|
760
872
|
return text.parse().ok().map(Lit::Int);
|
|
@@ -929,6 +1041,7 @@ pub(crate) fn refines_tuple(annotated: &str, inferred: &str) -> bool {
|
|
|
929
1041
|
ours.iter().zip(&theirs).all(|(a, i)| {
|
|
930
1042
|
let base = i.split('[').next().unwrap_or(i);
|
|
931
1043
|
a == i
|
|
1044
|
+
|| same_union(a, i)
|
|
932
1045
|
|| covers_literal(a, i)
|
|
933
1046
|
|| literal_pair(a, i).is_some_and(|pair| pair.same_nullability)
|
|
934
1047
|
|| *i == "typing.Any"
|
|
@@ -943,6 +1056,45 @@ pub(crate) fn refines_tuple(annotated: &str, inferred: &str) -> bool {
|
|
|
943
1056
|
}
|
|
944
1057
|
}
|
|
945
1058
|
|
|
1059
|
+
/// Whether every row of the inferred `tuple[...]` is a row of the annotated one: `str | None`
|
|
1060
|
+
/// admits `str`, `str` admits `Literal['a']`, and `object` or `typing.Any` admit anything.
|
|
1061
|
+
fn admits_tuple(annotated: &str, inferred: &str) -> bool {
|
|
1062
|
+
fn literal_class(member: &str) -> Option<&'static str> {
|
|
1063
|
+
let values = member
|
|
1064
|
+
.strip_prefix("typing.")
|
|
1065
|
+
.unwrap_or(member)
|
|
1066
|
+
.strip_prefix("Literal[")?
|
|
1067
|
+
.strip_suffix(']')?;
|
|
1068
|
+
let values = split_top(values, ',');
|
|
1069
|
+
if values.iter().all(|v| v.starts_with(['\'', '"'])) {
|
|
1070
|
+
Some("str")
|
|
1071
|
+
} else if values.iter().all(|v| matches!(*v, "True" | "False")) {
|
|
1072
|
+
Some("bool")
|
|
1073
|
+
} else if values.iter().all(|v| v.parse::<i64>().is_ok()) {
|
|
1074
|
+
Some("int")
|
|
1075
|
+
} else {
|
|
1076
|
+
None
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
fn admits(annotated: &str, inferred: &str) -> bool {
|
|
1080
|
+
let members = union_members(annotated);
|
|
1081
|
+
members
|
|
1082
|
+
.iter()
|
|
1083
|
+
.any(|m| matches!(*m, "typing.Any" | "Any" | "object"))
|
|
1084
|
+
|| split_top(inferred, '|').iter().all(|member| {
|
|
1085
|
+
members.contains(member)
|
|
1086
|
+
|| literal_class(member).is_some_and(|class| members.contains(&class))
|
|
1087
|
+
})
|
|
1088
|
+
}
|
|
1089
|
+
match (tuple_items(annotated), tuple_items(inferred)) {
|
|
1090
|
+
(Some(ours), Some(theirs)) => match ours.as_slice() {
|
|
1091
|
+
[each, "..."] => theirs.iter().all(|i| admits(each, i)),
|
|
1092
|
+
_ => ours.len() == theirs.len() && ours.iter().zip(&theirs).all(|(a, i)| admits(a, i)),
|
|
1093
|
+
},
|
|
1094
|
+
_ => false,
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
946
1098
|
#[cfg(test)]
|
|
947
1099
|
mod tests {
|
|
948
1100
|
use super::*;
|
|
@@ -1094,9 +1246,35 @@ mod tests {
|
|
|
1094
1246
|
false,
|
|
1095
1247
|
&[],
|
|
1096
1248
|
);
|
|
1097
|
-
let
|
|
1098
|
-
|
|
1099
|
-
|
|
1249
|
+
let narrow: Vec<_> = diags
|
|
1250
|
+
.iter()
|
|
1251
|
+
.filter(|d| d.rule == Rule::NarrowType)
|
|
1252
|
+
.collect();
|
|
1253
|
+
assert_eq!(narrow.len(), 1);
|
|
1254
|
+
assert!(narrow[0].message.contains("tuple[int]"));
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
#[test]
|
|
1258
|
+
fn test_wider_annotation_is_stale_not_narrow() {
|
|
1259
|
+
for (annotated, inferred, admitted) in [
|
|
1260
|
+
("tuple[str | None]", "tuple[str]", true),
|
|
1261
|
+
("tuple[object, typing.Any]", "tuple[int | None, str]", true),
|
|
1262
|
+
(
|
|
1263
|
+
"tuple[str, int, bool]",
|
|
1264
|
+
"tuple[Literal['a', \"b\"], typing.Literal[-1, 2], Literal[True]]",
|
|
1265
|
+
true,
|
|
1266
|
+
),
|
|
1267
|
+
("tuple[str]", "tuple[str | None]", false),
|
|
1268
|
+
("tuple[int]", "tuple[int, str]", false),
|
|
1269
|
+
("tuple[int]", "tuple[Literal['a']]", false),
|
|
1270
|
+
("tuple[int]", "dict[str, int]", false),
|
|
1271
|
+
] {
|
|
1272
|
+
assert_eq!(
|
|
1273
|
+
admits_tuple(annotated, inferred),
|
|
1274
|
+
admitted,
|
|
1275
|
+
"{annotated} {inferred}"
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1100
1278
|
}
|
|
1101
1279
|
|
|
1102
1280
|
#[test]
|
|
@@ -19,12 +19,13 @@ use crate::rules::{Rule, RuleConfig};
|
|
|
19
19
|
use crate::scan::Scan;
|
|
20
20
|
|
|
21
21
|
/// The rules whose diagnostics carry a fix.
|
|
22
|
-
const FIXABLE: [Rule;
|
|
22
|
+
const FIXABLE: [Rule; 7] = [
|
|
23
23
|
Rule::InlineSqlComment,
|
|
24
24
|
Rule::MysqlSqlMarker,
|
|
25
25
|
Rule::StaleRewrite,
|
|
26
26
|
Rule::SelectStar,
|
|
27
27
|
Rule::StaleType,
|
|
28
|
+
Rule::NarrowType,
|
|
28
29
|
Rule::UntypedQuery,
|
|
29
30
|
];
|
|
30
31
|
|
|
@@ -78,7 +79,7 @@ pub fn fix_source(
|
|
|
78
79
|
for edit in applied {
|
|
79
80
|
match rule_at[&(edit.range.start, edit.range.end)] {
|
|
80
81
|
Rule::StaleRewrite => wrapped.push(edit.replacement),
|
|
81
|
-
Rule::StaleType | Rule::UntypedQuery => {
|
|
82
|
+
Rule::StaleType | Rule::NarrowType | Rule::UntypedQuery => {
|
|
82
83
|
introduced.insert(edit.replacement);
|
|
83
84
|
}
|
|
84
85
|
_ => {}
|
|
@@ -38,8 +38,10 @@ pub enum Rule {
|
|
|
38
38
|
SelectStarNoSchema,
|
|
39
39
|
/// A `--sql` string that is not wrapped in `SQL[T](...)`.
|
|
40
40
|
StaleRewrite,
|
|
41
|
-
/// A `SQL[tuple[...]]`
|
|
41
|
+
/// A `SQL[tuple[...]]` wider than the inferred row type: it admits every row.
|
|
42
42
|
StaleType,
|
|
43
|
+
/// A `SQL[tuple[...]]` that does not admit every row the query returns.
|
|
44
|
+
NarrowType,
|
|
43
45
|
/// A `--sql` marker under MySQL, which rejects it.
|
|
44
46
|
MysqlSqlMarker,
|
|
45
47
|
/// `"--sql SELECT ..."`: the whole query is a SQL comment.
|
|
@@ -66,7 +68,7 @@ pub enum Rule {
|
|
|
66
68
|
|
|
67
69
|
impl Rule {
|
|
68
70
|
/// Every rule.
|
|
69
|
-
pub const ALL: [Rule;
|
|
71
|
+
pub const ALL: [Rule; 34] = [
|
|
70
72
|
Rule::ColumnCountMismatch,
|
|
71
73
|
Rule::ColumnTypeMismatch,
|
|
72
74
|
Rule::ColumnNameMismatch,
|
|
@@ -81,6 +83,7 @@ impl Rule {
|
|
|
81
83
|
Rule::SelectStarNoSchema,
|
|
82
84
|
Rule::StaleRewrite,
|
|
83
85
|
Rule::StaleType,
|
|
86
|
+
Rule::NarrowType,
|
|
84
87
|
Rule::MysqlSqlMarker,
|
|
85
88
|
Rule::InlineSqlComment,
|
|
86
89
|
Rule::FstringSkipped,
|
|
@@ -98,6 +101,7 @@ impl Rule {
|
|
|
98
101
|
Rule::Name(NameRule::UnknownType),
|
|
99
102
|
Rule::Name(NameRule::UnsupportedSyntax),
|
|
100
103
|
Rule::Name(NameRule::InsertMissingColumn),
|
|
104
|
+
Rule::Name(NameRule::InsertValueCount),
|
|
101
105
|
Rule::Name(NameRule::UngroupedColumn),
|
|
102
106
|
];
|
|
103
107
|
|
|
@@ -118,6 +122,7 @@ impl Rule {
|
|
|
118
122
|
Rule::SelectStarNoSchema => "select-star-no-schema",
|
|
119
123
|
Rule::StaleRewrite => "stale-rewrite",
|
|
120
124
|
Rule::StaleType => "stale-type",
|
|
125
|
+
Rule::NarrowType => "narrow-type",
|
|
121
126
|
Rule::MysqlSqlMarker => "mysql-sql-marker",
|
|
122
127
|
Rule::InlineSqlComment => "inline-sql-comment",
|
|
123
128
|
Rule::FstringSkipped => "fstring-skipped",
|
|
@@ -149,12 +154,14 @@ impl Rule {
|
|
|
149
154
|
| Rule::LiteralMismatch
|
|
150
155
|
| Rule::ParamCountMismatch
|
|
151
156
|
| Rule::ParamTypeMismatch
|
|
157
|
+
| Rule::NarrowType
|
|
152
158
|
| Rule::SelectStarNoSchema
|
|
153
159
|
| Rule::Name(
|
|
154
160
|
NameRule::UnknownTable
|
|
155
161
|
| NameRule::UnknownColumn
|
|
156
162
|
| NameRule::AmbiguousColumn
|
|
157
163
|
| NameRule::InsertMissingColumn
|
|
164
|
+
| NameRule::InsertValueCount
|
|
158
165
|
| NameRule::UngroupedColumn,
|
|
159
166
|
) => Severity::Error,
|
|
160
167
|
Rule::ColumnNameMismatch
|
|
@@ -310,6 +317,8 @@ mod tests {
|
|
|
310
317
|
let config = RuleConfig::default();
|
|
311
318
|
assert_eq!(config.severity(Rule::SelectStarNoSchema), Severity::Error);
|
|
312
319
|
assert_eq!(config.severity(Rule::StaleRewrite), Severity::Warn);
|
|
320
|
+
assert_eq!(config.severity(Rule::StaleType), Severity::Warn);
|
|
321
|
+
assert_eq!(config.severity(Rule::NarrowType), Severity::Error);
|
|
313
322
|
assert_eq!(config.severity(Rule::UnparsedQuery), Severity::Warn);
|
|
314
323
|
assert_eq!(config.severity(Rule::RedundantOptional), Severity::Ignore);
|
|
315
324
|
assert_eq!(
|
|
@@ -382,6 +382,19 @@ impl Call {
|
|
|
382
382
|
any_calls.binary_search(&self.span.start).is_ok()
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
/// `T` as a type: the content of a string literal (a forward reference, `SQL["User"]`),
|
|
386
|
+
/// else its source.
|
|
387
|
+
pub(crate) fn row_type<'a>(&self, source: &'a str) -> &'a str {
|
|
388
|
+
let written = source[self.annotation.clone()].trim();
|
|
389
|
+
['"', '\'']
|
|
390
|
+
.into_iter()
|
|
391
|
+
.find_map(|quote| {
|
|
392
|
+
let inner = written.strip_prefix(quote)?.strip_suffix(quote)?;
|
|
393
|
+
(!inner.contains([quote, '\\'])).then_some(inner.trim())
|
|
394
|
+
})
|
|
395
|
+
.unwrap_or(written)
|
|
396
|
+
}
|
|
397
|
+
|
|
385
398
|
/// The source of the argument's literals, from the first to the last.
|
|
386
399
|
pub(crate) fn argument<'a>(&self, source: &'a str) -> &'a str {
|
|
387
400
|
&source[self.strings[0].start..self.strings[self.strings.len() - 1].end]
|
|
@@ -38,7 +38,9 @@ fn collect(dir: &Path, files: &mut Vec<PathBuf>) {
|
|
|
38
38
|
let path = entry.path();
|
|
39
39
|
if path.is_dir() {
|
|
40
40
|
collect(&path, files);
|
|
41
|
-
} else if path.to_string_lossy().ends_with(".inference.toml")
|
|
41
|
+
} else if path.to_string_lossy().ends_with(".inference.toml")
|
|
42
|
+
|| path.ends_with("rules/dialects.toml")
|
|
43
|
+
{
|
|
42
44
|
files.push(path);
|
|
43
45
|
}
|
|
44
46
|
}
|