diff --git a/coverage/syntax/baseline.json b/coverage/syntax/baseline.json index c6a063756..921d2fa47 100644 --- a/coverage/syntax/baseline.json +++ b/coverage/syntax/baseline.json @@ -15,7 +15,7 @@ "catalogue/utility.toml": "55b30dcf06bf8182232a0da6b1da75462ae171e015cc8c9b8b95b36a722b44c5", "sources.toml": "47dbcf41cb7aa0fb2981d2fe9e0c33578a3eb53d80863685f2711c551ff0591e" }, - "parser_tree": "3e89af5774ad48e90b91c6416fca02a0517c4cb7", + "parser_tree": "0cca295bf9de498b61adfbc95c00f2d066d23251", "forms": { "sf.classes.anomaly-detection.commands.create-anomaly-detection": "verified", "sf.classes.anomaly-detection.commands.drop-anomaly-detection": "verified", @@ -230,7 +230,7 @@ "sf.ddl.alter-application.unset-tag": "verified", "sf.ddl.alter-application.upgrade": "verified", "sf.ddl.alter-application.upgrade-using": "verified", - "sf.ddl.alter-application.upgrade-using-version": "unverified", + "sf.ddl.alter-application.upgrade-using-version": "verified", "sf.ddl.alter-artifact-repository.set": "verified", "sf.ddl.alter-artifact-repository.set-tag": "verified", "sf.ddl.alter-artifact-repository.unset-comment-authentication-secret-package-retention": "verified", @@ -249,12 +249,12 @@ "sf.ddl.alter-backup-set.unset-comment": "verified", "sf.ddl.alter-backup-set.unset-tag": "verified", "sf.ddl.alter-compute-pool.set": "unverified", - "sf.ddl.alter-compute-pool.stop-all": "unverified", + "sf.ddl.alter-compute-pool.stop-all": "verified", "sf.ddl.alter-compute-pool.suspend-resume": "verified", "sf.ddl.alter-compute-pool.unset-auto-suspend-secs-auto-resume-placement-group": "verified", "sf.ddl.alter-contact.rename-to": "verified", "sf.ddl.alter-contact.set": "verified", - "sf.ddl.alter-cortex-search.add-scoring-profile": "unverified", + "sf.ddl.alter-cortex-search.add-scoring-profile": "verified", "sf.ddl.alter-cortex-search.drop-scoring-profile": "verified", "sf.ddl.alter-cortex-search.refresh": "verified", "sf.ddl.alter-cortex-search.set": "unverified", @@ -552,7 +552,7 @@ "sf.ddl.alter-streamlit.pull": "verified", "sf.ddl.alter-streamlit.push": "unverified", "sf.ddl.alter-streamlit.rename-to": "verified", - "sf.ddl.alter-streamlit.set": "unverified", + "sf.ddl.alter-streamlit.set": "verified", "sf.ddl.alter-streamlit.set.syntax": "verified", "sf.ddl.alter-streamlit.unset-secrets-external-access-integrations-query-warehouse": "unverified", "sf.ddl.alter-table-column.datagovnpolicytagaction": "unverified", @@ -681,7 +681,7 @@ "sf.ddl.create-data-metric-function.create-data-metric-function": "verified", "sf.ddl.create-data-metric-function.create-data-metric-function.create-or-alter-data-metric-function": "verified", "sf.ddl.create-data-metric-function.define-data-metric-function": "unverified", - "sf.ddl.create-data-movement-rule": "unverified", + "sf.ddl.create-data-movement-rule": "verified", "sf.ddl.create-database-catalog-linked": "unverified", "sf.ddl.create-database.create-database": "unverified", "sf.ddl.create-database.create-database-as-replica": "verified", @@ -777,7 +777,7 @@ "sf.ddl.create-openflow-connector.from": "verified", "sf.ddl.create-openflow-connector.from-definition": "verified", "sf.ddl.create-openflow-deployment": "verified", - "sf.ddl.create-openflow-runtime": "unverified", + "sf.ddl.create-openflow-runtime": "verified", "sf.ddl.create-pipe.create-or-alter-pipe": "verified", "sf.ddl.create-pipe.create-pipe-as": "verified", "sf.ddl.create-pipe.define-pipe": "verified", @@ -805,9 +805,9 @@ "sf.ddl.create-semantic-view.create-or-alter-semantic": "unverified", "sf.ddl.create-semantic-view.create-semantic-view-tables": "unverified", "sf.ddl.create-semantic-view.define-semantic-view": "unverified", - "sf.ddl.create-sequence.create-or-alter-sequence": "unverified", - "sf.ddl.create-sequence.create-sequence": "unverified", - "sf.ddl.create-sequence.define-sequence": "unverified", + "sf.ddl.create-sequence.create-or-alter-sequence": "verified", + "sf.ddl.create-sequence.create-sequence": "verified", + "sf.ddl.create-sequence.define-sequence": "verified", "sf.ddl.create-service": "unverified", "sf.ddl.create-snapshot": "verified", "sf.ddl.create-snapshot-set.database": "verified", @@ -846,7 +846,7 @@ "sf.ddl.create-table.create-or-alter-table": "unverified", "sf.ddl.create-table.create-table": "unverified", "sf.ddl.create-table.create-table-as": "verified", - "sf.ddl.create-table.create-table-clone": "unverified", + "sf.ddl.create-table.create-table-clone": "verified", "sf.ddl.create-table.create-table-from-archive": "verified", "sf.ddl.create-table.create-table-from-backup": "unverified", "sf.ddl.create-table.create-table-like": "verified", @@ -995,7 +995,7 @@ "sf.ddl.drop-view": "verified", "sf.ddl.drop-warehouse": "verified", "sf.ddl.experiment.alter-experiment-abort": "verified", - "sf.ddl.experiment.create-experiment": "unverified", + "sf.ddl.experiment.create-experiment": "verified", "sf.ddl.experiment.describe-desc-experiment": "verified", "sf.ddl.experiment.drop-experiment": "verified", "sf.ddl.experiment.execute-experiment": "verified", @@ -1143,7 +1143,7 @@ "sf.ddl.truncate-materialized-view": "unverified", "sf.ddl.undrop-database": "verified", "sf.ddl.undrop-dynamic-table": "verified", - "sf.ddl.undrop-external-volume": "unverified", + "sf.ddl.undrop-external-volume": "verified", "sf.ddl.undrop-iceberg-table": "verified", "sf.ddl.undrop-notebook": "verified", "sf.ddl.undrop-schema": "unverified", @@ -1272,7 +1272,7 @@ "sf.expressions.sql-format-models": "absent", "sf.expressions.virtual-columns.alter-table-add-as": "absent", "sf.expressions.virtual-columns.create-or-replace-table": "absent", - "sf.functions.abs": "unverified", + "sf.functions.abs": "verified", "sf.functions.accumulate": "unverified", "sf.functions.acos": "verified", "sf.functions.acosh": "verified", @@ -1449,7 +1449,7 @@ "sf.functions.check-xml.check-xml-str": "verified", "sf.functions.chr": "verified", "sf.functions.classify-text-snowflake-cortex": "verified", - "sf.functions.coalesce": "unverified", + "sf.functions.coalesce": "verified", "sf.functions.collate.collate": "verified", "sf.functions.collate.collate.syntax": "verified", "sf.functions.collation": "verified", @@ -1551,8 +1551,8 @@ "sf.functions.deregister-extension": "verified", "sf.functions.div0": "verified", "sf.functions.div0null": "verified", - "sf.functions.dmf-accepted-values": "unverified", - "sf.functions.dmf-accepted-values-percent": "unverified", + "sf.functions.dmf-accepted-values": "verified", + "sf.functions.dmf-accepted-values-percent": "verified", "sf.functions.dmf-accuracy": "verified", "sf.functions.dmf-all-space-count": "verified", "sf.functions.dmf-all-space-percent": "verified", @@ -1703,7 +1703,7 @@ "sf.functions.dmf-outlier-zscore-count": "verified", "sf.functions.dmf-outlier-zscore-percent": "verified", "sf.functions.dmf-r-squared": "verified", - "sf.functions.dmf-referential-integrity-count.table": "unverified", + "sf.functions.dmf-referential-integrity-count.table": "verified", "sf.functions.dmf-referential-integrity-count.table.syntax": "unverified", "sf.functions.dmf-rmse": "verified", "sf.functions.dmf-row-count-difference": "verified", @@ -1719,7 +1719,7 @@ "sf.functions.dmf-rows-in-past-year-percent": "verified", "sf.functions.dmf-seconds-since-row-count-change": "verified", "sf.functions.dmf-skew": "verified", - "sf.functions.dmf-source-target-lag.alter-table-add-data": "unverified", + "sf.functions.dmf-source-target-lag.alter-table-add-data": "verified", "sf.functions.dmf-source-target-lag.snowflake-core-source-target-lag": "verified", "sf.functions.dmf-special-character-count": "verified", "sf.functions.dmf-special-character-percent": "verified", @@ -2806,7 +2806,7 @@ "sf.security_admin.alter-account.remove-organization-user-group": "unverified", "sf.security_admin.alter-account.rename-to": "verified", "sf.security_admin.alter-account.set": "unverified", - "sf.security_admin.alter-account.set-authentication-session-policy": "unverified", + "sf.security_admin.alter-account.set-authentication-session-policy": "verified", "sf.security_admin.alter-account.set-contact": "verified", "sf.security_admin.alter-account.set-data-movement-policy": "verified", "sf.security_admin.alter-account.set-domain-names": "verified", @@ -2821,7 +2821,7 @@ "sf.security_admin.alter-account.set-tag": "verified", "sf.security_admin.alter-account.set-tenant-type-internal-external": "verified", "sf.security_admin.alter-account.unset": "verified", - "sf.security_admin.alter-account.unset-authentication-session-policy": "unverified", + "sf.security_admin.alter-account.unset-authentication-session-policy": "verified", "sf.security_admin.alter-account.unset-contact": "verified", "sf.security_admin.alter-account.unset-data-movement-policy": "verified", "sf.security_admin.alter-account.unset-domain-names": "verified", @@ -2976,7 +2976,7 @@ "sf.security_admin.alter-organization-account.unset-tag": "unverified", "sf.security_admin.alter-organization-profile.abort": "verified", "sf.security_admin.alter-organization-profile.add-live-version-from": "verified", - "sf.security_admin.alter-organization-profile.add-version-from": "unverified", + "sf.security_admin.alter-organization-profile.add-version-from": "verified", "sf.security_admin.alter-organization-profile.as": "unverified", "sf.security_admin.alter-organization-profile.commit": "verified", "sf.security_admin.alter-organization-profile.publish": "unverified", @@ -2987,8 +2987,8 @@ "sf.security_admin.alter-organization-user-group.remove-organization-users": "verified", "sf.security_admin.alter-organization-user-group.set-is-grantable-true-false": "verified", "sf.security_admin.alter-organization-user-group.set-visibility-all-accounts": "verified", - "sf.security_admin.alter-organization-user.set": "unverified", - "sf.security_admin.alter-organization-user.unset": "unverified", + "sf.security_admin.alter-organization-user.set": "verified", + "sf.security_admin.alter-organization-user.unset": "verified", "sf.security_admin.alter-organization.set-default-tenant-type-internal-external": "verified", "sf.security_admin.alter-organization.set-domain-names": "verified", "sf.security_admin.alter-organization.unset-domain-names": "verified", @@ -3122,7 +3122,7 @@ "sf.security_admin.alter-user-modify-key-pair.unset": "verified", "sf.security_admin.alter-user-modify-programmatic-access-token.rename-to": "verified", "sf.security_admin.alter-user-modify-programmatic-access-token.set": "verified", - "sf.security_admin.alter-user-modify-programmatic-access-token.unset": "unverified", + "sf.security_admin.alter-user-modify-programmatic-access-token.unset": "verified", "sf.security_admin.alter-user-modify-workload-identity.rename-to": "verified", "sf.security_admin.alter-user-modify-workload-identity.set": "verified", "sf.security_admin.alter-user-modify-workload-identity.unset": "unverified", @@ -3140,7 +3140,7 @@ "sf.security_admin.alter-user.remove-delegated-authorizations-of": "verified", "sf.security_admin.alter-user.rename-to": "unverified", "sf.security_admin.alter-user.reset-password": "verified", - "sf.security_admin.alter-user.set": "unverified", + "sf.security_admin.alter-user.set": "verified", "sf.security_admin.alter-user.set-authentication-password-session": "unverified", "sf.security_admin.alter-user.set-tag": "verified", "sf.security_admin.alter-user.unset": "verified", @@ -3179,7 +3179,7 @@ "sf.security_admin.create-database-role.create-or-alter-database": "verified", "sf.security_admin.create-database-role.define-database-role": "unverified", "sf.security_admin.create-external-access-integration": "verified", - "sf.security_admin.create-failover-group.as-replica-of": "unverified", + "sf.security_admin.create-failover-group.as-replica-of": "verified", "sf.security_admin.create-failover-group.object-types-allowed-accounts": "unverified", "sf.security_admin.create-feature-policy": "verified", "sf.security_admin.create-ingress-policy": "verified", @@ -3204,20 +3204,20 @@ "sf.security_admin.create-notification-integration-queue-outbound-azure": "verified", "sf.security_admin.create-notification-integration-queue-outbound-gcp": "verified", "sf.security_admin.create-notification-integration-webhooks": "verified", - "sf.security_admin.create-organization-account": "unverified", + "sf.security_admin.create-organization-account": "verified", "sf.security_admin.create-organization-listing.as": "verified", "sf.security_admin.create-organization-listing.from": "verified", - "sf.security_admin.create-organization-profile.profile": "unverified", + "sf.security_admin.create-organization-profile.profile": "verified", "sf.security_admin.create-organization-profile.profile-as": "unverified", "sf.security_admin.create-organization-profile.profile-from": "verified", - "sf.security_admin.create-organization-user": "unverified", + "sf.security_admin.create-organization-user": "verified", "sf.security_admin.create-organization-user-group": "verified", "sf.security_admin.create-packages-policy": "verified", "sf.security_admin.create-password-policy": "verified", "sf.security_admin.create-privacy-policy": "verified", "sf.security_admin.create-projection-policy": "verified", - "sf.security_admin.create-replication-group.as-replica-of": "unverified", - "sf.security_admin.create-replication-group.object-types-allowed-accounts": "unverified", + "sf.security_admin.create-replication-group.as-replica-of": "verified", + "sf.security_admin.create-replication-group.object-types-allowed-accounts": "verified", "sf.security_admin.create-resource-monitor": "unverified", "sf.security_admin.create-role.create-or-alter-role": "verified", "sf.security_admin.create-role.create-role": "verified", @@ -53183,6 +53183,7 @@ "sf.ddl.undrop-external-volume.o002", "sf.ddl.undrop-external-volume.o003", "sf.ddl.undrop-external-volume.o004", + "sf.ddl.undrop-external-volume.o005", "sf.ddl.undrop-external-volume.o006", "sf.ddl.undrop-external-volume.o007", "sf.ddl.undrop-external-volume.o008", @@ -56691,6 +56692,7 @@ "sf.functions.abs.o005", "sf.functions.abs.o006", "sf.functions.abs.o007", + "sf.functions.abs.o008", "sf.functions.accumulate.o002", "sf.functions.accumulate.o004", "sf.functions.accumulate.o006", @@ -58456,6 +58458,7 @@ "sf.functions.coalesce.o008", "sf.functions.coalesce.o009", "sf.functions.coalesce.o010", + "sf.functions.coalesce.o011", "sf.functions.coalesce.o012", "sf.functions.coalesce.o013", "sf.functions.collate.collate.o001", @@ -73376,6 +73379,7 @@ "sf.security_admin.alter-account.set-maintenance-policy-for.o002", "sf.security_admin.alter-account.set-maintenance-policy-for.o003", "sf.security_admin.alter-account.set-maintenance-policy-for.o004", + "sf.security_admin.alter-account.set-maintenance-policy-for.o005", "sf.security_admin.alter-account.set-maintenance-policy-for.o006", "sf.security_admin.alter-account.set-maintenance-policy-for.o007", "sf.security_admin.alter-account.set-maintenance-policy-for.o008", @@ -76786,6 +76790,7 @@ "sf.security_admin.alter-organization-user.set.o040", "sf.security_admin.alter-organization-user.set.o041", "sf.security_admin.alter-organization-user.set.o042", + "sf.security_admin.alter-organization-user.set.o044", "sf.security_admin.alter-organization-user.set.o045", "sf.security_admin.alter-organization-user.set.o046", "sf.security_admin.alter-organization-user.set.o047", @@ -76798,6 +76803,7 @@ "sf.security_admin.alter-organization-user.unset.o002", "sf.security_admin.alter-organization-user.unset.o003", "sf.security_admin.alter-organization-user.unset.o004", + "sf.security_admin.alter-organization-user.unset.o042", "sf.security_admin.alter-organization-user.unset.o043", "sf.security_admin.alter-organization-user.unset.o045", "sf.security_admin.alter-organization-user.unset.o046", @@ -83199,6 +83205,7 @@ "sf.security_admin.create-connection.connection-as-replica-of.o007", "sf.security_admin.create-connection.connection-as-replica-of.o008", "sf.security_admin.create-connection.connection-as-replica-of.o009", + "sf.security_admin.create-connection.connection-as-replica-of.o010", "sf.security_admin.create-connection.connection-as-replica-of.o011", "sf.security_admin.create-connection.connection-as-replica-of.o012", "sf.security_admin.create-connection.connection-as-replica-of.o013", @@ -83403,6 +83410,7 @@ "sf.security_admin.create-failover-group.as-replica-of.o007", "sf.security_admin.create-failover-group.as-replica-of.o008", "sf.security_admin.create-failover-group.as-replica-of.o009", + "sf.security_admin.create-failover-group.as-replica-of.o010", "sf.security_admin.create-failover-group.as-replica-of.o011", "sf.security_admin.create-failover-group.as-replica-of.o012", "sf.security_admin.create-failover-group.as-replica-of.o013", @@ -84529,6 +84537,7 @@ "sf.security_admin.create-notification-integration-webhooks.o060", "sf.security_admin.create-notification-integration-webhooks.o061", "sf.security_admin.create-organization-account.o001", + "sf.security_admin.create-organization-account.o002", "sf.security_admin.create-organization-account.o003", "sf.security_admin.create-organization-account.o004", "sf.security_admin.create-organization-account.o005", @@ -84574,6 +84583,8 @@ "sf.security_admin.create-organization-account.o045", "sf.security_admin.create-organization-account.o046", "sf.security_admin.create-organization-account.o047", + "sf.security_admin.create-organization-account.o048", + "sf.security_admin.create-organization-account.o049", "sf.security_admin.create-organization-account.o051", "sf.security_admin.create-organization-account.o052", "sf.security_admin.create-organization-account.o053", @@ -84665,9 +84676,11 @@ "sf.security_admin.create-organization-profile.profile.o001", "sf.security_admin.create-organization-profile.profile.o002", "sf.security_admin.create-organization-profile.profile.o003", + "sf.security_admin.create-organization-profile.profile.o004", "sf.security_admin.create-organization-profile.profile.o005", "sf.security_admin.create-organization-profile.profile.o006", "sf.security_admin.create-organization-profile.profile.o007", + "sf.security_admin.create-organization-profile.profile.o008", "sf.security_admin.create-organization-profile.profile.o009", "sf.security_admin.create-organization-profile.profile.o010", "sf.security_admin.create-organization-profile.profile.o011", @@ -84732,6 +84745,7 @@ "sf.security_admin.create-organization-user.o001", "sf.security_admin.create-organization-user.o002", "sf.security_admin.create-organization-user.o003", + "sf.security_admin.create-organization-user.o004", "sf.security_admin.create-organization-user.o005", "sf.security_admin.create-organization-user.o006", "sf.security_admin.create-organization-user.o007", @@ -84775,6 +84789,7 @@ "sf.security_admin.create-organization-user.o045", "sf.security_admin.create-organization-user.o046", "sf.security_admin.create-organization-user.o047", + "sf.security_admin.create-organization-user.o048", "sf.security_admin.create-organization-user.o049", "sf.security_admin.create-organization-user.o050", "sf.security_admin.create-organization-user.o051", @@ -85067,6 +85082,7 @@ "sf.security_admin.create-replication-group.as-replica-of.o007", "sf.security_admin.create-replication-group.as-replica-of.o008", "sf.security_admin.create-replication-group.as-replica-of.o009", + "sf.security_admin.create-replication-group.as-replica-of.o010", "sf.security_admin.create-replication-group.as-replica-of.o011", "sf.security_admin.create-replication-group.as-replica-of.o012", "sf.security_admin.create-replication-group.as-replica-of.o013", @@ -113274,6 +113290,7 @@ "sf.ddl.undrop-dynamic-table.k2b72e781a9bd", "sf.ddl.undrop-dynamic-table.ka077c76c3131", "sf.ddl.undrop-external-volume.k1c72fbeb15e0", + "sf.ddl.undrop-external-volume.kb557e7ff4586", "sf.ddl.undrop-external-volume.kb77fe649b342", "sf.ddl.undrop-external-volume.ke8393e3618ed", "sf.ddl.undrop-iceberg-table.k01f54a38ec58", @@ -114707,6 +114724,7 @@ "sf.expressions.snowflake-db-classes.kf09e47361402", "sf.expressions.snowflake-db-classes.kf7884278b104", "sf.functions.abs.k2c81ac0cf981", + "sf.functions.abs.k93f76764032f", "sf.functions.abs.k9da3f129e3df", "sf.functions.acos.k01bd50c84d39", "sf.functions.acosh.k642e930348a4", @@ -115079,6 +115097,7 @@ "sf.functions.classify-text-snowflake-cortex.k37dc905636c4", "sf.functions.classify-text-snowflake-cortex.kbad57ff198dc", "sf.functions.coalesce.k119d9c3e7187", + "sf.functions.coalesce.k25882bf706ba", "sf.functions.coalesce.k66abc4034420", "sf.functions.coalesce.ka4ce739e47da", "sf.functions.collate.collate.k9f4919da118f", @@ -120097,6 +120116,7 @@ "sf.security_admin.alter-organization-user-group.set-visibility-all-accounts.kde7c41a3c93d", "sf.security_admin.alter-organization-user-group.set-visibility-all-accounts.kfb24a514c8d4", "sf.security_admin.alter-organization-user.set.k208a549656e7", + "sf.security_admin.alter-organization-user.set.k4614b5f6b6ce", "sf.security_admin.alter-organization-user.set.k4d0ca810d66e", "sf.security_admin.alter-organization-user.set.k5c35f72dfd7b", "sf.security_admin.alter-organization-user.set.k5d12dd267060", @@ -120114,6 +120134,7 @@ "sf.security_admin.alter-organization-user.unset.k4698b9020f70", "sf.security_admin.alter-organization-user.unset.k46a7447b71bd", "sf.security_admin.alter-organization-user.unset.k993043dcfecf", + "sf.security_admin.alter-organization-user.unset.k996ab4d41cd1", "sf.security_admin.alter-organization-user.unset.k9b7a20e662a7", "sf.security_admin.alter-organization-user.unset.kab5bd9bf7dfd", "sf.security_admin.alter-organization-user.unset.kb035f670ea1e", @@ -121966,6 +121987,7 @@ "sf.security_admin.create-connection.connection-as-replica-of.kc33cfcefc61a", "sf.security_admin.create-connection.connection-as-replica-of.kd9485b076672", "sf.security_admin.create-connection.connection-as-replica-of.ke7d6a46981e7", + "sf.security_admin.create-connection.connection-as-replica-of.kee0d007198dd", "sf.security_admin.create-connection.connection.k13a3463a9f4b", "sf.security_admin.create-connection.connection.k83787192dc8c", "sf.security_admin.create-connection.connection.k8ad769b60ace", @@ -122053,6 +122075,7 @@ "sf.security_admin.create-failover-group.as-replica-of.k4788ebe125ee", "sf.security_admin.create-failover-group.as-replica-of.k50ded129e55e", "sf.security_admin.create-failover-group.as-replica-of.k5591ba2fb41a", + "sf.security_admin.create-failover-group.as-replica-of.k5e081373c209", "sf.security_admin.create-failover-group.as-replica-of.kc5f0c572b695", "sf.security_admin.create-failover-group.as-replica-of.ke778f6e49235", "sf.security_admin.create-failover-group.object-types-allowed-accounts.k0da25cbb5fe7", @@ -122390,7 +122413,10 @@ "sf.security_admin.create-organization-account.k6cc79630610d", "sf.security_admin.create-organization-account.k7874d968a227", "sf.security_admin.create-organization-account.k78766b20857a", + "sf.security_admin.create-organization-account.k839222785fe7", "sf.security_admin.create-organization-account.kbfe07d4685ab", + "sf.security_admin.create-organization-account.kcf702494458a", + "sf.security_admin.create-organization-account.kff1992709592", "sf.security_admin.create-organization-listing.as.k0edf866f1e3c", "sf.security_admin.create-organization-listing.as.k7de93b82036b", "sf.security_admin.create-organization-listing.as.kc57d8b5d2353", @@ -122422,10 +122448,12 @@ "sf.security_admin.create-organization-profile.profile-from.kd6a275ea65db", "sf.security_admin.create-organization-profile.profile-from.kde9075a65c07", "sf.security_admin.create-organization-profile.profile-from.kf4175fc8b162", + "sf.security_admin.create-organization-profile.profile.k0c40743be9b8", "sf.security_admin.create-organization-profile.profile.k88b9f8902687", "sf.security_admin.create-organization-profile.profile.ka442895af050", "sf.security_admin.create-organization-profile.profile.kb43d848e38c5", "sf.security_admin.create-organization-profile.profile.kbda58681b938", + "sf.security_admin.create-organization-profile.profile.kf3cbe549deb2", "sf.security_admin.create-organization-user-group.k00f80439b95c", "sf.security_admin.create-organization-user-group.k024ff599bce5", "sf.security_admin.create-organization-user-group.k4318aab765b7", @@ -122447,6 +122475,7 @@ "sf.security_admin.create-organization-user.kb3c237cc130f", "sf.security_admin.create-organization-user.kbc51cd74a1c4", "sf.security_admin.create-organization-user.kf01ae16152cf", + "sf.security_admin.create-organization-user.kf135125eeecb", "sf.security_admin.create-packages-policy.k06b7ddc63f4b", "sf.security_admin.create-packages-policy.k190e27aa911d", "sf.security_admin.create-packages-policy.k4e062169c83a", @@ -122547,6 +122576,7 @@ "sf.security_admin.create-projection-policy.kc9aef1f4312b", "sf.security_admin.create-replication-group.as-replica-of.k03b25bc0a669", "sf.security_admin.create-replication-group.as-replica-of.k2672e8c2258f", + "sf.security_admin.create-replication-group.as-replica-of.k28cad1926c65", "sf.security_admin.create-replication-group.as-replica-of.k3bd263ec38ea", "sf.security_admin.create-replication-group.as-replica-of.k42e066792f56", "sf.security_admin.create-replication-group.as-replica-of.k50021556c9a6", @@ -127225,19 +127255,7 @@ "sf.utility.explain.kf562011f3a6d" ], "refused_cases": [ - "sf.ddl.alter-stage.set.syntax.kd30e04e36906", - "sf.ddl.undrop-external-volume.kb557e7ff4586", - "sf.security_admin.alter-organization-user.set.k4614b5f6b6ce", - "sf.security_admin.alter-organization-user.unset.k996ab4d41cd1", - "sf.security_admin.create-connection.connection-as-replica-of.kee0d007198dd", - "sf.security_admin.create-failover-group.as-replica-of.k5e081373c209", - "sf.security_admin.create-organization-account.k839222785fe7", - "sf.security_admin.create-organization-account.kcf702494458a", - "sf.security_admin.create-organization-account.kff1992709592", - "sf.security_admin.create-organization-profile.profile.k0c40743be9b8", - "sf.security_admin.create-organization-profile.profile.kf3cbe549deb2", - "sf.security_admin.create-organization-user.kf135125eeecb", - "sf.security_admin.create-replication-group.as-replica-of.k28cad1926c65" + "sf.ddl.alter-stage.set.syntax.kd30e04e36906" ], "syntax_error_cases_rejected": [ "b5-p3-04-counter_for_loop_closed_by_end_for", @@ -154312,6 +154330,7 @@ "sf.security_admin.alter-account.set-maintenance-policy-for.k7d68d7442373", "sf.security_admin.alter-account.set-maintenance-policy-for.k8e3d3ccbfe57", "sf.security_admin.alter-account.set-maintenance-policy-for.kacf1e1da2285", + "sf.security_admin.alter-account.set-maintenance-policy-for.kd79fe523c612", "sf.security_admin.alter-account.set-maintenance-policy-for.kea12a80554ef", "sf.security_admin.alter-account.set-packages-password-multi.k06d7b8080b42", "sf.security_admin.alter-account.set-packages-password-multi.k535ea9b891a2", @@ -163690,7 +163709,6 @@ "sf.functions.system-explain-plan-json.k34b4a27a76b5", "sf.functions.to-query.k52f4b25a2004", "sf.functions.to-query.kdfe11763c33e", - "sf.security_admin.alter-account.set-maintenance-policy-for.kd79fe523c612", "sf.transaction_session.show-parameters.k087afefdac13", "sf.utility.execute-inference-job-service.kd484841cc87f", "sf.utility.execute-inference-job-service.kfa70a352f258" @@ -164563,10 +164581,8 @@ "sf.ddl.create-table.create-table.k294b45271663", "sf.ddl.create-table.create-table.k81e6795ae05a", "sf.ddl.drop-iceberg-table.k0c6d88e813a7", - "sf.ddl.undrop-external-volume.kb557e7ff4586", "sf.expressions.snowflake-db-classes.k9eccc2186fa2", "sf.functions.abs.k6169455e23b9", - "sf.functions.abs.k93f76764032f", "sf.functions.acos.k9277749f1514", "sf.functions.acosh.k2bfcf40e4874", "sf.functions.ai-summarize-agg.k96152ee79950", @@ -164603,7 +164619,6 @@ "sf.functions.ceil.kadb89c6ce35e", "sf.functions.check-xml.check-xml.kebab6f35e2a9", "sf.functions.chr.ke91db843fd30", - "sf.functions.coalesce.k25882bf706ba", "sf.functions.collate.collate.syntax.kd9832a5c1f19", "sf.functions.cos.k5ffbc006170e", "sf.functions.cosh.k26b88d9dac0b", @@ -164724,18 +164739,6 @@ "sf.query.operators-query.except.k5a8b5eec3643", "sf.query.select.select.selecting-specific-columns.k2883fa022666", "sf.scripting.begin.k0fb78add2575", - "sf.security_admin.alter-account.set-maintenance-policy-for.kd79fe523c612", - "sf.security_admin.alter-organization-user.set.k4614b5f6b6ce", - "sf.security_admin.alter-organization-user.unset.k996ab4d41cd1", - "sf.security_admin.create-connection.connection-as-replica-of.kee0d007198dd", - "sf.security_admin.create-failover-group.as-replica-of.k5e081373c209", - "sf.security_admin.create-organization-account.k839222785fe7", - "sf.security_admin.create-organization-account.kcf702494458a", - "sf.security_admin.create-organization-account.kff1992709592", - "sf.security_admin.create-organization-profile.profile.k0c40743be9b8", - "sf.security_admin.create-organization-profile.profile.kf3cbe549deb2", - "sf.security_admin.create-organization-user.kf135125eeecb", - "sf.security_admin.create-replication-group.as-replica-of.k28cad1926c65", "sf.security_admin.create-resource-monitor.k1a411efdd4d8", "sf.security_admin.create-resource-monitor.k1da4b22723a7", "sf.security_admin.create-resource-monitor.k1dc94f7a8149", diff --git a/coverage/syntax/receipt-disagreements.toml b/coverage/syntax/receipt-disagreements.toml index 284a30efd..880ee96e9 100644 --- a/coverage/syntax/receipt-disagreements.toml +++ b/coverage/syntax/receipt-disagreements.toml @@ -371,7 +371,6 @@ other_receipts_contradicted = [ "k52f4b25a2004", "kdfe11763c33e", ] -"sf.security_admin.alter-account.set-maintenance-policy-for" = ["kd79fe523c612"] "sf.transaction_session.show-parameters" = ["k087afefdac13"] "sf.utility.execute-inference-job-service" = [ "kd484841cc87f", @@ -380,19 +379,3 @@ other_receipts_contradicted = [ [parsed_cases_refused] "sf.ddl.alter-stage.set.syntax" = ["kd30e04e36906"] -"sf.ddl.undrop-external-volume" = ["kb557e7ff4586"] -"sf.security_admin.alter-organization-user.set" = ["k4614b5f6b6ce"] -"sf.security_admin.alter-organization-user.unset" = ["k996ab4d41cd1"] -"sf.security_admin.create-connection.connection-as-replica-of" = ["kee0d007198dd"] -"sf.security_admin.create-failover-group.as-replica-of" = ["k5e081373c209"] -"sf.security_admin.create-organization-account" = [ - "k839222785fe7", - "kcf702494458a", - "kff1992709592", -] -"sf.security_admin.create-organization-profile.profile" = [ - "k0c40743be9b8", - "kf3cbe549deb2", -] -"sf.security_admin.create-organization-user" = ["kf135125eeecb"] -"sf.security_admin.create-replication-group.as-replica-of" = ["k28cad1926c65"] diff --git a/coverage/syntax/reviews/ddl.toml b/coverage/syntax/reviews/ddl.toml index f1511e12e..114c92e03 100644 --- a/coverage/syntax/reviews/ddl.toml +++ b/coverage/syntax/reviews/ddl.toml @@ -4237,3 +4237,112 @@ note = "Approved at its last review with the same definition, obligations and ca "sf.ddl.create-tag.create-tag" = "da99d3b7e73ff860170b43d19e1c172a97300f20efc627c4fbb0b2c786cb3925" "sf.ddl.create-tag.define-tag" = "788f08aa9ca96d9d8f54777d086bdf8f17b0ee629381486171f23b3f82ddb127" "sf.ddl.create-versioned-schema" = "6383231c1aed459dbe09d220aab7116af32a1d4b152ab52cb1427846cd02b166" + +[[review]] +id = "ddl-043" +family = "ddl" +parser_tree = "0cca295bf9de498b61adfbc95c00f2d066d23251" +reviewed_utc = "2026-11-15T04:17:54Z" +verdict = "approved" +forms = [ + "sf.ddl.alter-cortex-search.drop-scoring-profile", + "sf.ddl.alter-data-movement-rule.set-max-rows-as-returns", + "sf.ddl.alter-external-volume.update-storage-location-credentials-aws-key-id", + "sf.ddl.alter-function.set", + "sf.ddl.alter-function.set-headers", + "sf.ddl.alter-iceberg-table-version-tag.drop-version-tag", + "sf.ddl.alter-listing.listing-add-version-from", + "sf.ddl.alter-notebook.set", + "sf.ddl.alter-service.fromspecification-fromspecificationtemplate", + "sf.ddl.alter-stage.set", + "sf.ddl.alter-stage.set-directory-enable-true", + "sf.ddl.create-gateway", + "sf.ddl.create-model.model", + "sf.ddl.create-openflow-connector.from", + "sf.ddl.create-openflow-connector.from-definition", +] +note = "Approved at its last review with the same definition, obligations and cases; the cases whose parse trees changed with the parser at 548fe5bda, which keeps keywords, key-value pairs and omitted name parts in their own fields, were read again under review brief version 1, and every scored case agrees with its Snowflake receipt." + +[review.fingerprints] +"sf.ddl.alter-cortex-search.drop-scoring-profile" = "8e4036eea485f7e8a5d3c17f9d6b02371c99e45b0c12c9642a29618615d1ea87" +"sf.ddl.alter-data-movement-rule.set-max-rows-as-returns" = "c16b657daf0c84d5e7ac9bdf505df9666445e2f35c157572064109486a0e998f" +"sf.ddl.alter-external-volume.update-storage-location-credentials-aws-key-id" = "e655f23ab3bcfa58499bb64287b7da5219cd5c12834e92881f11751af9aa0955" +"sf.ddl.alter-function.set" = "50feca5997a051fe819b69273e3fcd7344729e4ace3df817215bb1d5282208c0" +"sf.ddl.alter-function.set-headers" = "645259d8ff9ac96209f3a82731b9d5a1af0ec596d4d648b5dc0ec54b4a640a70" +"sf.ddl.alter-iceberg-table-version-tag.drop-version-tag" = "97118b84ec912ec4e312bf4a74022e89d5bb033929f37802ff64a1f7f229ad18" +"sf.ddl.alter-listing.listing-add-version-from" = "4ba1e7c871ff282c7935e1a5c98bbb6e2edb6fbb6feec380d69b1ea36e53e874" +"sf.ddl.alter-notebook.set" = "bfbb6bbb29a9b6e6cd75d835e135759ae49a97a512cdc6318761c7cce42a1b5d" +"sf.ddl.alter-service.fromspecification-fromspecificationtemplate" = "7e6d5f258c96121ccde71517ae2aacf8455bd9968c9cbb5b806706e9f2310266" +"sf.ddl.alter-stage.set" = "dcc135b27019640eed6b6f903f7dc7973f6e6d09606dbb5f88ff0a6ef5cafb31" +"sf.ddl.alter-stage.set-directory-enable-true" = "3589bd15fcb85c051168b600e77d46cebabb471fab26dec4735dfd991f1ca45c" +"sf.ddl.create-gateway" = "369343cf2a9f860c71a361be9b5b165560103945e12168809e6adb81a4af91e6" +"sf.ddl.create-model.model" = "f27c46af0becf410b8a98371d52982f3468bd8507deabb7c599c95cf5498c00f" +"sf.ddl.create-openflow-connector.from" = "e7736caa107e7aadb561c79120cf1106f0f41a668d76762351ed1cbf7c436400" +"sf.ddl.create-openflow-connector.from-definition" = "77b1a50729af558fa851ea834ca263964a90e377235fb4a2d8009f0918c8d4be" + +[review.definitions] +"sf.ddl.alter-cortex-search.drop-scoring-profile" = "4f962619d813f2ac79ac64b53dad353b63b901c021266cd77926333cbbaab80e" +"sf.ddl.alter-data-movement-rule.set-max-rows-as-returns" = "8572fe2a90171f3be739044a7b05360259a67c530871a982d1ef44ae5cf7e5db" +"sf.ddl.alter-external-volume.update-storage-location-credentials-aws-key-id" = "c0280728d0f4de8036fe0dccbc6ae78df8db99b0b1e8219ba87ffd8da86d8b49" +"sf.ddl.alter-function.set" = "d714479280ab160fa10a59c8723c6514d8e718295db1d87d27d8b8cd8bf2bd9d" +"sf.ddl.alter-function.set-headers" = "38995b57b10cdd3eb7510950e16fe5484ba90ab156b9a8220f8b76dd51e22b2f" +"sf.ddl.alter-iceberg-table-version-tag.drop-version-tag" = "c0686669412efdf5bc737a60fdd0c81074f11b61f3c52cdd300b08e4f7704187" +"sf.ddl.alter-listing.listing-add-version-from" = "d2b6b9d6850402fbf3cb027e387305cef8a9e1f08f883561b58bdf7cbca3d380" +"sf.ddl.alter-notebook.set" = "faba8e8fc6371ca2cf22841e6e9d64bd521ea114a3128286e0459ed4deb8a9c4" +"sf.ddl.alter-service.fromspecification-fromspecificationtemplate" = "e056da280443d8ffe942530dda82de2c17f85e62e5c8481f057f72e7b122d27a" +"sf.ddl.alter-stage.set" = "4036d1cf417bcef8312e0799e586abd3db1b78b8921662a0dfd1026589239025" +"sf.ddl.alter-stage.set-directory-enable-true" = "df151ec6045eef01ae04d23f0acf73e699613df1040941b2251b47ca318d9311" +"sf.ddl.create-gateway" = "b2449339ed642e417a8051f44099f278e5346544136fe70aa03f74c1bdf93bed" +"sf.ddl.create-model.model" = "6f4b59e3f853f43628a158f218bfc2dcd72176a108ccbab4cae309ae2922e029" +"sf.ddl.create-openflow-connector.from" = "024f89daa1eae75e6d70157aed21ce242bcabf8f2a59944d9443d64356ba7e64" +"sf.ddl.create-openflow-connector.from-definition" = "3d95b8e7437b458b789d4593613d6bedc939bc51e9fcdeed390b03da13663011" + +[[review]] +id = "ddl-044" +family = "ddl" +parser_tree = "0cca295bf9de498b61adfbc95c00f2d066d23251" +reviewed_utc = "2026-11-15T04:17:56Z" +verdict = "approved" +forms = [ + "sf.ddl.alter-application.upgrade-using-version", + "sf.ddl.alter-compute-pool.stop-all", + "sf.ddl.alter-cortex-search.add-scoring-profile", + "sf.ddl.alter-streamlit.set", + "sf.ddl.create-data-movement-rule", + "sf.ddl.create-openflow-runtime", + "sf.ddl.create-sequence.create-or-alter-sequence", + "sf.ddl.create-sequence.create-sequence", + "sf.ddl.create-sequence.define-sequence", + "sf.ddl.create-table.create-table-clone", + "sf.ddl.experiment.create-experiment", + "sf.ddl.undrop-external-volume", +] +note = "Read in full under review brief version 1 against the documentation page, the obligations, the cases and the parse trees of the parser at 548fe5bda; every scored case agrees with its Snowflake receipt." + +[review.fingerprints] +"sf.ddl.alter-application.upgrade-using-version" = "efb0fea29df73f1c00cd6b20f1120023fbd4f7864ee420df15953ab82acffdf1" +"sf.ddl.alter-compute-pool.stop-all" = "0981e9f37a159e94778c32e29c888173342beb95b13defdec93c968c936060be" +"sf.ddl.alter-cortex-search.add-scoring-profile" = "d0b9c6fd6b3fc6292fb82e02b10bb837a477042c66e51d22f3700a46190037ec" +"sf.ddl.alter-streamlit.set" = "054e874b930b0241e73558bcc3b0b6c15f48ad2a038daef6512156feb1760ba3" +"sf.ddl.create-data-movement-rule" = "1a498b7d6b40be26c94c222f87668c39a10d99e8b075fbe16bb3606419997d90" +"sf.ddl.create-openflow-runtime" = "395e75ef8681034fbdcab6b3d25c57389bbe622da1db260df04fdafac99aec30" +"sf.ddl.create-sequence.create-or-alter-sequence" = "ae9c5d2f072229d901f11ca8fc29a239937e8e7f172820cb5b1849a0b9bef8ce" +"sf.ddl.create-sequence.create-sequence" = "9b7e1344c7b84fe501e50f141284462cc3042bdd7d5d7b0414d9bec35b97265d" +"sf.ddl.create-sequence.define-sequence" = "bb1c636d9ac7a56b47613c33dd63900a7c41ed6f117cdbb493fee783025d75af" +"sf.ddl.create-table.create-table-clone" = "582bb6dab3f3b4f4b05af44596b55ef78f3e9f01e70bf2e06e81c8ee1860b9aa" +"sf.ddl.experiment.create-experiment" = "b5ddb6a953d937db4ede0f7ac0aac778cfa7bc617c73a34d86ea61416f7f2797" +"sf.ddl.undrop-external-volume" = "eb8f669ec3eea211aad6cfa7487228e36139672babc4fa1350bf009fd9fcfeaf" + +[review.definitions] +"sf.ddl.alter-application.upgrade-using-version" = "77d8d92fbec092f23770862ceee5146bd7ccf2d38cd4c8242327dedb9a3dd934" +"sf.ddl.alter-compute-pool.stop-all" = "de4cb12516489444f5acdb5537a53ecab9577a18e3bf9b9913bbcc3e28f90ba5" +"sf.ddl.alter-cortex-search.add-scoring-profile" = "bf9c593fe9af17594f55cfdeada19c115f56f17f8e7ab9498f75fe28a2f3fe2e" +"sf.ddl.alter-streamlit.set" = "b1c640342348018c6b7c810dc46bc5b82a382837cb6d739baa6aabe202bcd038" +"sf.ddl.create-data-movement-rule" = "700ab60144013c2a34310036d0165ea00415d401c1695402cd1a1d1f83e4f2b2" +"sf.ddl.create-openflow-runtime" = "43a9931584a407d6ef079c21342d496156277ba62fe920354e8c3d88d205108b" +"sf.ddl.create-sequence.create-or-alter-sequence" = "02d5df7ce84571ef03286dc957f79b9c631f9e769bfcdbfb1763d654cc200b47" +"sf.ddl.create-sequence.create-sequence" = "2140ce342defcdff90c93602f7b8654b64db3819e6d06c24c4af55ecb292ca3c" +"sf.ddl.create-sequence.define-sequence" = "cde1f9045a1afd7322eaa5ac2212747b4a0aaa0a7e22864d9474a12b2cff3c99" +"sf.ddl.create-table.create-table-clone" = "fc141db0872a5504e4750af33aa4b51c07665287ed8fbf0aedac0ec66cd11a50" +"sf.ddl.experiment.create-experiment" = "c45d7e4d3c3690b2d1d97937f8d33d8ef52900d9adabe76c3e64f2ab7c00edc9" +"sf.ddl.undrop-external-volume" = "a9ea5bd96c8fd93cec48dfa8af88a70a050c296b7d54d985426a23cb37644ece" diff --git a/coverage/syntax/reviews/functions.toml b/coverage/syntax/reviews/functions.toml index 6d3844b94..9b2fa7d6e 100644 --- a/coverage/syntax/reviews/functions.toml +++ b/coverage/syntax/reviews/functions.toml @@ -6466,3 +6466,52 @@ note = "Read in full under review brief version 1 against the documentation page "sf.functions.get-ai-observability-events-snowflake-local" = "cbe0d75551bde8512d6d420461141aba335cf5756b894b121e13d60e879a3d7b" "sf.functions.get-ai-observability-logs-snowflake-local" = "2ae530230cdfe96a6c26d9199bae708ddf6f67f504b2499e6fe9ae69278a0de9" "sf.functions.get-ai-record-trace-snowflake-local" = "9540cdeb323e5cb881562f6d0a298154ceeac305c3d0776583d2dc4f9f10ddf7" + +[[review]] +id = "functions-032" +family = "functions" +parser_tree = "0cca295bf9de498b61adfbc95c00f2d066d23251" +reviewed_utc = "2026-11-15T04:17:54Z" +verdict = "approved" +forms = [ + "sf.functions.dmf-row-count-difference", +] +note = "Approved at its last review with the same definition, obligations and cases; the cases whose parse trees changed with the parser at 548fe5bda, which keeps keywords, key-value pairs and omitted name parts in their own fields, were read again under review brief version 1, and every scored case agrees with its Snowflake receipt." + +[review.fingerprints] +"sf.functions.dmf-row-count-difference" = "8e736fdd6a13d8d26bbd52aaa9ebeb01ae2344d1082f58353fca6f2f01e3bd6f" + +[review.definitions] +"sf.functions.dmf-row-count-difference" = "6633e608b8a64fa01efefe1b7f05518779304ec781c22423f740b5e9042a3f90" + +[[review]] +id = "functions-033" +family = "functions" +parser_tree = "0cca295bf9de498b61adfbc95c00f2d066d23251" +reviewed_utc = "2026-11-15T04:17:56Z" +verdict = "approved" +forms = [ + "sf.functions.abs", + "sf.functions.coalesce", + "sf.functions.dmf-accepted-values", + "sf.functions.dmf-accepted-values-percent", + "sf.functions.dmf-referential-integrity-count.table", + "sf.functions.dmf-source-target-lag.alter-table-add-data", +] +note = "Read in full under review brief version 1 against the documentation page, the obligations, the cases and the parse trees of the parser at 548fe5bda; every scored case agrees with its Snowflake receipt." + +[review.fingerprints] +"sf.functions.abs" = "01362a8cbd3887b0b45a5ffd9e737bd58348645afcca38d36ca7230cd829f1cc" +"sf.functions.coalesce" = "89d274afe91c10e650dc71fcd6ab4d570682bfc9864855c619dfbfc25a52f6e6" +"sf.functions.dmf-accepted-values" = "9022763e092c00d8d60ff582713bfb8a6baee88acd5dcf2ebe7a9a929bb1bdfb" +"sf.functions.dmf-accepted-values-percent" = "835afdc605a242a91f6b625ac8ff35a04c74bf1e80fb3c2e0642510a8ab1529c" +"sf.functions.dmf-referential-integrity-count.table" = "ff32a6d4143f4e1f42ce3aa6ffc14608dcd8fbac812f5463258a42be03b8a437" +"sf.functions.dmf-source-target-lag.alter-table-add-data" = "d073ee5f3ef9f2b2a64b1ff2da09aa7d8e5fa0a90cebaa74e6588c0ea1845ea3" + +[review.definitions] +"sf.functions.abs" = "be5235fa19680039066d239814f876e6941c86b4814300a90d19b1612d2c5b4e" +"sf.functions.coalesce" = "ea2354d1459479e4f06f6dfc2067e0cac6f4a6d401289860353f9687ce6d9aed" +"sf.functions.dmf-accepted-values" = "0f43871a9bf53475e95d04f9b58e8a146db0992b0c5c0209a7f5c4e5e77922f1" +"sf.functions.dmf-accepted-values-percent" = "13194a834f982364851a9aedff65d3248cb2ce29343fd8438fb7fdc775261c4c" +"sf.functions.dmf-referential-integrity-count.table" = "47c14d85203c383d248c0eef06e65fd37693ef72606d8105541250e62f1a49f9" +"sf.functions.dmf-source-target-lag.alter-table-add-data" = "820f46969fb8b72bdd6bb35fa792ed4e6a2e81073393417a907c7263befce7e0" diff --git a/coverage/syntax/reviews/security-admin.toml b/coverage/syntax/reviews/security-admin.toml index c2eb69a5a..43c5372be 100644 --- a/coverage/syntax/reviews/security-admin.toml +++ b/coverage/syntax/reviews/security-admin.toml @@ -3173,3 +3173,115 @@ note = "Approved at its last review with the same definition, obligations and ca "sf.security_admin.create-storage-integration-postgres-internal" = "76481f8cc8ceb166d463046715305012b82e4e52875fc69fb8da8b0a38ef38bb" "sf.security_admin.create-storage-lifecycle-policy" = "8665b98dca1048abdea0c35f7990f3f66bc17b725ceca8daee0b251d4ea25610" "sf.security_admin.create-user" = "80b02e9a4f791284926b9a3bed2a6c7e8462272bc8701be2658208175a0aa1b4" + +[[review]] +id = "security-admin-030" +family = "security_admin" +parser_tree = "0cca295bf9de498b61adfbc95c00f2d066d23251" +reviewed_utc = "2026-11-15T04:17:54Z" +verdict = "approved" +forms = [ + "sf.security_admin.alter-account.set-feature-policy-for", + "sf.security_admin.alter-account.set-feature-policy-for-2", + "sf.security_admin.alter-account.set-feature-policy-for.altering-the-current-account", + "sf.security_admin.alter-account.unset-feature-policy-for", + "sf.security_admin.alter-account.unset-feature-policy-for-2", + "sf.security_admin.alter-account.unset-feature-policy-for.altering-the-current-account", + "sf.security_admin.alter-account.unset-maintenance-policy-for", + "sf.security_admin.alter-failover-group.set-object-types-integrations-allowed-integration-types", + "sf.security_admin.alter-organization-profile.add-live-version-from", + "sf.security_admin.alter-replication-group.set-object-types-integrations-allowed-integration-types", + "sf.security_admin.alter-user-add-programmatic-access-token", + "sf.security_admin.alter-user-modify-programmatic-access-token.rename-to", + "sf.security_admin.alter-user-modify-programmatic-access-token.set", + "sf.security_admin.alter-user-remove-programmatic-access-token", + "sf.security_admin.alter-user-rotate-programmatic-access-token", +] +note = "Approved at its last review with the same definition, obligations and cases; the cases whose parse trees changed with the parser at 548fe5bda, which keeps keywords, key-value pairs and omitted name parts in their own fields, were read again under review brief version 1, and every scored case agrees with its Snowflake receipt." + +[review.fingerprints] +"sf.security_admin.alter-account.set-feature-policy-for" = "c66af12641714e4329a402c945b09490323eb4dbc0916fc2411b03961b39645a" +"sf.security_admin.alter-account.set-feature-policy-for-2" = "3f21fa6346c325290a514a711f18363bee2a3a01759daf6c6fd9253a5ea98ca6" +"sf.security_admin.alter-account.set-feature-policy-for.altering-the-current-account" = "4eabf5a08375162b8085b1f9d2331f769d58fac70e9a680b1674487fa697bd29" +"sf.security_admin.alter-account.unset-feature-policy-for" = "e70d7987f85e329c70cf95bf71d97527ce4c935dedd8cf2c4ea0c21b04a67d50" +"sf.security_admin.alter-account.unset-feature-policy-for-2" = "207d4753e0cafd78c62b450980a26bf7bd2f820f8fe3d0179ed8625bca1d3840" +"sf.security_admin.alter-account.unset-feature-policy-for.altering-the-current-account" = "b0c8cc45dd89cfcf3e895cbdfa562a61ac6c5bd40fd553c7bbd5d32d8e89a148" +"sf.security_admin.alter-account.unset-maintenance-policy-for" = "26b53e5e4a93a91bc15ab4234311a65684727f1d8a34dfeb7376582279e0846e" +"sf.security_admin.alter-failover-group.set-object-types-integrations-allowed-integration-types" = "e2e571cfc5d19301fb58d7663a2a050655b2db43d84d8d45a1410e3cd9e354b9" +"sf.security_admin.alter-organization-profile.add-live-version-from" = "cf0b124697416739eac89e097498af26f2c2ec7eacc44f86bf84ab19ee55d5d1" +"sf.security_admin.alter-replication-group.set-object-types-integrations-allowed-integration-types" = "530bb6fb5da8732f184c5cf7b8d06658cd84d6a00a3e8f0e82597d726003fc04" +"sf.security_admin.alter-user-add-programmatic-access-token" = "7e7af9b367a9b37f7a99377c22c3fb5448bc6af773a436c18220bdd2a5d9fea1" +"sf.security_admin.alter-user-modify-programmatic-access-token.rename-to" = "aabff3eec475f43f2243f963dc21fc9c2fdb62cccbd528296f5aecc0640e8306" +"sf.security_admin.alter-user-modify-programmatic-access-token.set" = "caedc806b09eeafb510762ef7cd2b24acf7f42989c19725e54f5b84e74e17b60" +"sf.security_admin.alter-user-remove-programmatic-access-token" = "d2d3f5be6dcc113a9b08961d39e83a7e83733322fcf3ddbbc4388e86f4a9268d" +"sf.security_admin.alter-user-rotate-programmatic-access-token" = "2fd3b4e05f6c9ae804686878a1c06781f1aad9d46da802ab982dba30552777a2" + +[review.definitions] +"sf.security_admin.alter-account.set-feature-policy-for" = "808878a0491177a939d0fb15b198ec0f7b4e18b5fea92b198516a302bb1a13d4" +"sf.security_admin.alter-account.set-feature-policy-for-2" = "d8a88b1132a3d51f67508a1aee93f1723b5be246293c869cb834dc436a350b75" +"sf.security_admin.alter-account.set-feature-policy-for.altering-the-current-account" = "9c9f6a6753f6e7448346023ed98e3e625c879b1569f6050eb745758e7c3d28bd" +"sf.security_admin.alter-account.unset-feature-policy-for" = "8453d8ca4ab2500dcd916b0f205fbb82210c77303a4de02e1e9712233d42686a" +"sf.security_admin.alter-account.unset-feature-policy-for-2" = "6a81cc0253920b5286cbe6572ed2b6de462800d11e9c6bbd1129838dfdca0f05" +"sf.security_admin.alter-account.unset-feature-policy-for.altering-the-current-account" = "c3ddf7c6507748f340d772a6ef2fb72fe6133cfdeaa805ad6e385d09e8a5206d" +"sf.security_admin.alter-account.unset-maintenance-policy-for" = "994d6a5da1aa135911012915782c5fd1b8e863e4c7a9eb7a4d191c6bd5126e81" +"sf.security_admin.alter-failover-group.set-object-types-integrations-allowed-integration-types" = "a2bba8661f4227b8d26e65c348fde817e9a705b2cbc7e1007057b542259bd0ba" +"sf.security_admin.alter-organization-profile.add-live-version-from" = "6a7749b99110a0a0dd5688adfabc262526d38b0139dcc185b4030560ac44be62" +"sf.security_admin.alter-replication-group.set-object-types-integrations-allowed-integration-types" = "97712b285f83607ad848369368df68b00fe33bdcd124accd1770ea16853aa204" +"sf.security_admin.alter-user-add-programmatic-access-token" = "950416cefc3db0faf416ef071dcc708a696b230d6211053efe7c6f99ba7bd065" +"sf.security_admin.alter-user-modify-programmatic-access-token.rename-to" = "cd64f608ce8631449eeecd44b34e70ea6ac94e692ebc60f8b29ab969b41e41a0" +"sf.security_admin.alter-user-modify-programmatic-access-token.set" = "78968d0e01a9d477ce82b06c5b00d895c96e60b87e61acbd385001dc88108ac1" +"sf.security_admin.alter-user-remove-programmatic-access-token" = "90ef6b5ef46d82197fc786377ba3054f38796488e7d80bf6b7c129af30429e15" +"sf.security_admin.alter-user-rotate-programmatic-access-token" = "76da2ed28f7265dc0b60e5c12693d38733db7233ee5ca49baa29748fa5fa553b" + +[[review]] +id = "security-admin-031" +family = "security_admin" +parser_tree = "0cca295bf9de498b61adfbc95c00f2d066d23251" +reviewed_utc = "2026-11-15T04:17:56Z" +verdict = "approved" +forms = [ + "sf.security_admin.alter-account.set-authentication-session-policy", + "sf.security_admin.alter-account.unset-authentication-session-policy", + "sf.security_admin.alter-organization-profile.add-version-from", + "sf.security_admin.alter-organization-user.set", + "sf.security_admin.alter-organization-user.unset", + "sf.security_admin.alter-user-modify-programmatic-access-token.unset", + "sf.security_admin.alter-user.set", + "sf.security_admin.create-failover-group.as-replica-of", + "sf.security_admin.create-organization-account", + "sf.security_admin.create-organization-profile.profile", + "sf.security_admin.create-organization-user", + "sf.security_admin.create-replication-group.as-replica-of", + "sf.security_admin.create-replication-group.object-types-allowed-accounts", +] +note = "Read in full under review brief version 1 against the documentation page, the obligations, the cases and the parse trees of the parser at 548fe5bda; every scored case agrees with its Snowflake receipt." + +[review.fingerprints] +"sf.security_admin.alter-account.set-authentication-session-policy" = "9afef0f505d7e236b51fd7879af660dab85d743901f2dee0cf0d884558759e2e" +"sf.security_admin.alter-account.unset-authentication-session-policy" = "5d6199ed6f6cd8daa78f354e36b6d79eeddb844eb7a8a181981fcf262bc4755a" +"sf.security_admin.alter-organization-profile.add-version-from" = "5ec5717fd4f2a698876d4763464fbb203a7b85ad566bee99d96034f1e5fa97fb" +"sf.security_admin.alter-organization-user.set" = "effaed837c24b49e548b3516175cbfe1334ba4a32053f87f8448a077f8375b78" +"sf.security_admin.alter-organization-user.unset" = "22a56a56fdbc7284dd1c75b195c30b7a92b9baa681793ddbcc4e39295e108d60" +"sf.security_admin.alter-user-modify-programmatic-access-token.unset" = "aa2e6df08ffea6249bd4b73873927547c9369f0b3aeefaf8d4a800398af1ffb4" +"sf.security_admin.alter-user.set" = "2bc598caf966e0f7dcc0bb56793da8d6aa1822b614d298fc15b6146ab261595c" +"sf.security_admin.create-failover-group.as-replica-of" = "55aeef335ea8d187efca3437f877db53e534a96d3a47b9188dd3c125ee3a22bd" +"sf.security_admin.create-organization-account" = "1ebf869d2c93c6418755a676c507c28418128b06168b01b6a76d582c2d9fad73" +"sf.security_admin.create-organization-profile.profile" = "e1f2401f84280d87d855c083104a0b2d8ff1c652021e8a22d578d9c86fba7bc0" +"sf.security_admin.create-organization-user" = "e97550a715f6cd54d369a4a9c34070c6ee8bf523d32cccd4ac3ae3a6b993c0ed" +"sf.security_admin.create-replication-group.as-replica-of" = "3d30d1e38f8d2fd92701f5b2db8a4bf31126a0b29bcf82ffee48a1176f376705" +"sf.security_admin.create-replication-group.object-types-allowed-accounts" = "c387a592ec922f1b86944553898dc080620495c3f8151ea76e2a7502026b5779" + +[review.definitions] +"sf.security_admin.alter-account.set-authentication-session-policy" = "a392562e7ceb3710c8427f52caa13fd367365a95e54d1c211094ae9342fe8e12" +"sf.security_admin.alter-account.unset-authentication-session-policy" = "5c659ec1234be8ffc1deb28c0867706b3b48bfb880920492fca925581bd634d2" +"sf.security_admin.alter-organization-profile.add-version-from" = "2b73fd4e8df4cb80fe575cf1669b45cff2cea5b75a5d2a92551f446bd4e8be67" +"sf.security_admin.alter-organization-user.set" = "48e629da8bbc812ff8fadf5573e0488f9a9734155c7539f2f9c6190903da8905" +"sf.security_admin.alter-organization-user.unset" = "f28349e92cb55a0095ca6dfa340230e7082380216cb4ce425e6c13679958fe4c" +"sf.security_admin.alter-user-modify-programmatic-access-token.unset" = "551eaecaee197460f0834fbb80ce76d4f62f00425ecd9cd674e32dbb08bbff4e" +"sf.security_admin.alter-user.set" = "c43fd1a03cd2d4d09e5a44e2a4063b90c663d01524f0bbe5237d40af2b065965" +"sf.security_admin.create-failover-group.as-replica-of" = "a1fcecb52304ef2690b591c06754e58e926cc1e4d0ff374ec63e543ce0027729" +"sf.security_admin.create-organization-account" = "cc4b2c9b87c5a4116904813c400e26e9f6c3d1732bf8554e15d77c0ee7afbed7" +"sf.security_admin.create-organization-profile.profile" = "bd9a88ac7b956bb7e7f212873ecf2236308d2f12b1af203fe98547351e031922" +"sf.security_admin.create-organization-user" = "66bea5bc0f65e7c955dc9a715de69e79a6986baefb36ead5b6203cdf4f0f6588" +"sf.security_admin.create-replication-group.as-replica-of" = "934cd8d5b644b257cc91e50fef6c48fc8c82a909f69b3a321a90cf8bf4d9d0a0" +"sf.security_admin.create-replication-group.object-types-allowed-accounts" = "04ac21af3aad40eeb81c032996c2f537a470451620b5929494dcec1c036d84e2" diff --git a/coverage/syntax/reviews/utility.toml b/coverage/syntax/reviews/utility.toml index db5d8e11f..682465fc9 100644 --- a/coverage/syntax/reviews/utility.toml +++ b/coverage/syntax/reviews/utility.toml @@ -279,3 +279,20 @@ note = "Read in full under review brief version 1 against the documentation page [review.definitions] "sf.utility.execute-job-service" = "698e7e3c000c21f8956c0bc257ba54e2af95a14b450e96a23ccdf4f61c906605" + +[[review]] +id = "utility-014" +family = "utility" +parser_tree = "0cca295bf9de498b61adfbc95c00f2d066d23251" +reviewed_utc = "2026-11-15T04:17:54Z" +verdict = "approved" +forms = [ + "sf.utility.execute-job-service", +] +note = "Approved at its last review with the same definition, obligations and cases; the cases whose parse trees changed with the parser at 548fe5bda, which keeps keywords, key-value pairs and omitted name parts in their own fields, were read again under review brief version 1, and every scored case agrees with its Snowflake receipt." + +[review.fingerprints] +"sf.utility.execute-job-service" = "b34d7de74b3d1054325ee6bc4931066e932c24a9d341aff337cf1dfcab1413bc" + +[review.definitions] +"sf.utility.execute-job-service" = "698e7e3c000c21f8956c0bc257ba54e2af95a14b450e96a23ccdf4f61c906605" diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_function.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_function.rs index 6ec1853d3..fe961a0b9 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_function.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_function.rs @@ -4,6 +4,7 @@ use crate::parser::_helpers::ddl::snowflake_routines::nullability_property; use crate::parser::models::Parser; use sqlquack_syntax::expressions::columns::models::ColumnDef; use sqlquack_syntax::expressions::schema_objects::main::record_function_property::record_function_property; +use sqlquack_syntax::expressions::schema_objects::models::BareValue; use sqlquack_syntax::expressions::schema_objects::models::CreateFunction; use sqlquack_syntax::expressions::schema_objects::models::FunctionBody; use sqlquack_syntax::expressions::schema_objects::models::FunctionPropertyKind; @@ -90,6 +91,8 @@ impl Parser { handler_uses_eq: false, runtime_version: None, packages: None, + runtime_version_bare: None, + packages_bare: Vec::new(), parameter_style: None, snowflake_properties: return_nullability .into_iter() @@ -440,12 +443,22 @@ impl Parser { || self.check(TokenType::Number) || self.is_identifier_or_keyword_token() { + function.runtime_version_bare = self.bare_value(); function.runtime_version = Some(self.advance_text()?); } function = record_function_property(function, FunctionPropertyKind::RuntimeVersion); Ok(function) } + /// How the current token writes a value that is usually a string: none for a string. + fn bare_value(&self) -> Option { + match self.peek().token_type { + TokenType::String => None, + TokenType::Number => Some(BareValue::Number), + _ => Some(BareValue::Name), + } + } + fn parse_create_function_packages( &mut self, mut function: CreateFunction, @@ -460,6 +473,9 @@ impl Parser { || self.check(TokenType::Number) || self.check_keyword() { + if let Some(bare) = self.bare_value() { + function.packages_bare.push((parsed_packages.len(), bare)); + } parsed_packages.push(self.advance_text()?); } else { break; diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_table.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_table.rs index 1c0f279e8..fbc3d6ea2 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_table.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_table.rs @@ -475,6 +475,7 @@ impl Parser { uuid, with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_table_forms.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_table_forms.rs index bfd1baced..368fac77d 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_table_forms.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/create_table_forms.rs @@ -151,6 +151,7 @@ impl Parser { uuid: None, with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } @@ -318,6 +319,7 @@ impl Parser { uuid: uuid.clone(), with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } @@ -384,6 +386,7 @@ impl Parser { uuid: uuid.clone(), with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } @@ -489,6 +492,7 @@ impl Parser { uuid: uuid.clone(), with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } @@ -584,6 +588,7 @@ impl Parser { uuid: uuid.clone(), with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } @@ -654,6 +659,7 @@ impl Parser { uuid: uuid.clone(), with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } @@ -720,6 +726,7 @@ impl Parser { uuid: uuid.clone(), with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } @@ -793,6 +800,7 @@ impl Parser { uuid: uuid.clone(), with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } @@ -869,6 +877,7 @@ impl Parser { uuid: uuid.clone(), with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, }))) } } diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter.rs index af73c3dba..0592e5e9a 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter.rs @@ -9,8 +9,12 @@ use sqlquack_syntax::expressions::functions::models::Function; use sqlquack_syntax::expressions::names::main::identifier_from_token::identifier_from_token; use sqlquack_syntax::expressions::names::models::Identifier; use sqlquack_syntax::expressions::names::models::TableRef; +use sqlquack_syntax::expressions::operators::models::BinaryOp; +use sqlquack_syntax::expressions::operators::models::NamedArgSeparator; +use sqlquack_syntax::expressions::operators::models::NamedArgument; use sqlquack_syntax::expressions::schema_objects::models::AlterObject; use sqlquack_syntax::expressions::schema_objects::models::ObjectProperty; +use sqlquack_syntax::expressions::statements::models::Var; use sqlquack_syntax::expressions::tree::models::Expression; use sqlquack_syntax::expressions::types::models::Tuple; use sqlquack_syntax::tokens::models::TokenType; @@ -833,7 +837,7 @@ impl Parser { } } - /// Parse `( [ [ , ... ] ] )`, each item a value or a `'' = ` pair. + /// Parse `( [ [ , ... ] ] )`, each item a value or a ` = ` pair. pub(super) fn parse_routine_value_list(&mut self) -> Result { self.expect_token(TokenType::LParen)?; let mut items: Vec = Vec::new(); @@ -841,9 +845,7 @@ impl Parser { loop { let item = self.parse_object_value()?; items.push(if self.match_token(TokenType::Eq) { - Expression::Tuple(Box::new(Tuple { - expressions: vec![item, self.parse_object_value()?], - })) + keyed_pair(item, self.parse_object_value()?, false) } else { item }); @@ -865,20 +867,32 @@ pub(super) fn alter_object( (operands, properties): (Vec, Vec), ) -> Expression { let named = !name.name.name.is_empty() || name.name.quoted; + let (action, operand_if_exists, operand_if_not_exists) = operand_existence(action); Expression::AlterObject(Box::new(AlterObject { kind: kind.to_owned(), name: named.then_some(name), arguments: Vec::new(), if_exists, action, - operand_if_exists: false, - operand_if_not_exists: false, + operand_if_exists, + operand_if_not_exists, operands, properties, ..Default::default() })) } +/// The action without the IF [ NOT ] EXISTS an action parser wrote into it for its own object, such as `ADD SCORING PROFILE IF NOT EXISTS`, with whether it said IF EXISTS and whether it said IF NOT EXISTS. +fn operand_existence(mut action: String) -> (String, bool, bool) { + for (words, not) in [(" IF NOT EXISTS", true), (" IF EXISTS", false)] { + if let Some(at) = action.find(words) { + action.replace_range(at..at + words.len(), ""); + return (action, !not, not); + } + } + (action, false, false) +} + /// A property's kind from its name: upper-cased when unquoted, as written when quoted. pub(super) fn property_kind(name: &Identifier) -> String { match name.quoted { @@ -895,6 +909,40 @@ pub(super) fn parameter_property(name: &Identifier, value: Option) - } } +/// A ` = ` pair, or ` => ` (`arrow`) as a named argument when the key is a plain name. +pub(super) fn keyed_pair(key: Expression, value: Expression, arrow: bool) -> Expression { + match key { + Expression::Table(table) if arrow && table.schema.is_none() && table.catalog.is_none() => { + Expression::NamedArgument(Box::new(NamedArgument { + name: table.name, + value, + separator: NamedArgSeparator::DArrow, + })) + } + key if arrow => Expression::Tuple(Box::new(Tuple { + expressions: vec![key, value], + })), + key => Expression::PropertyEQ(Box::new(BinaryOp::new(key, value))), + } +} + +/// A value written as a name, or as a keyword phrase such as `SECURITY INTEGRATIONS` when `more` words followed a plain one-part name; after a quoted or qualified name the words stay part of its last part. +pub(super) fn value_phrase(mut first: Box, more: Vec) -> Expression { + if more.is_empty() { + return Expression::Table(first); + } + if first.name.quoted || first.schema.is_some() || first.catalog.is_some() { + first.name.name = std::iter::once(first.name.name) + .chain(more) + .collect::>() + .join(" "); + return Expression::Table(first); + } + let words = std::iter::once(first.name.name).chain(more); + let this = words.collect::>().join(" ").to_ascii_uppercase(); + Expression::Var(Box::new(Var { this })) +} + /// One property of kind `kind`. pub(super) fn property( kind: &str, diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_account.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_account.rs index 66ef2719f..311ad91dd 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_account.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_account.rs @@ -1,10 +1,12 @@ //! Parser ddl: Snowflake ALTER of account-level objects: failover and replication groups, connections, integrations and organization objects. use crate::parser::_helpers::ddl::snowflake_alter::alter_object; +use crate::parser::_helpers::ddl::snowflake_alter::keyed_pair; use crate::parser::_helpers::ddl::snowflake_alter::named_property; use crate::parser::_helpers::ddl::snowflake_alter::parameter_property; use crate::parser::_helpers::ddl::snowflake_alter::property; use crate::parser::_helpers::ddl::snowflake_alter::property_kind; +use crate::parser::_helpers::ddl::snowflake_alter::value_phrase; use crate::parser::constants::SNOWFLAKE_GROUP_NAME_PROPERTIES; use crate::parser::constants::SNOWFLAKE_STRING_KEY_MAP_PROPERTIES; use crate::parser::models::Parser; @@ -146,19 +148,19 @@ impl Parser { return Err(self.parse_error(format!("Expected a name in the list of {key}"))); } let value = self.parse_object_value()?; - let Expression::Table(mut name) = value else { + let Expression::Table(name) = value else { values.push(value); if !self.match_token(TokenType::Comma) { break; } continue; }; + let mut more: Vec = Vec::new(); while !self.is_at_end() && self.check(TokenType::Var) && !self.check_next(TokenType::Eq) { - name.name.name = format!("{} {}", name.name.name, self.peek_text()); - self.skip(); + more.push(self.advance_text()?); } - values.push(Expression::Table(name)); + values.push(value_phrase(name, more)); if !self.match_token(TokenType::Comma) { break; } @@ -268,15 +270,16 @@ impl Parser { true => vec![self.parse_group_name()?], false => vec![], }; - let force = set && self.match_identifier("FORCE"); + let mut properties: Vec = Vec::new(); if let Some(scope) = self.match_policy_scope() { - action.push_str(&format!(" FOR ALL {scope}")); + properties.push(property(&format!("FOR ALL {scope}"), None, None)); + } + if set && self.match_identifier("FORCE") { + if properties.is_empty() && self.check_text_seq(&["FOR", "ALL"]) { + return Err(self.parse_error("FORCE comes after FOR ALL ")); + } + properties.push(property("FORCE", None, None)); } - let forced = set && (force || self.match_identifier("FORCE")); - let properties = match forced { - true => vec![property("FORCE", None, None)], - false => vec![], - }; (operands, properties) } else if self.check_identifier("TAG") && !self.check_next(TokenType::Eq) @@ -458,9 +461,7 @@ impl Parser { }; self.expect_token(TokenType::Eq)?; let value = self.parse_object_value()?; - items.push(Expression::Tuple(Box::new(Tuple { - expressions: vec![key, value], - }))); + items.push(keyed_pair(key, value, false)); } else { items.push(self.parse_object_value()?); } @@ -503,7 +504,7 @@ impl Parser { Ok(alter_object("CONNECTION", head, action, parts)) } - /// Parse ALTER ORGANIZATION after its first word: the organization itself, or its ACCOUNT, PROFILE, USER or USER GROUP. + /// Parse ALTER ORGANIZATION after its first word: a named organization's RENAME TO, SET or UNSET, the current one's SET or UNSET, or its ACCOUNT, PROFILE, USER or USER GROUP. fn parse_alter_organization(&mut self) -> Result { if self.match_text_seq(&["USER", "GROUP"]) { return self.parse_alter_organization_user_group(); @@ -539,14 +540,22 @@ impl Parser { (vec![], properties), )); } - let renames = self - .tokens - .get(self.current + 1) - .is_some_and(|token| token.text.eq_ignore_ascii_case("RENAME")); - if renames { + let named = match self.text_at(1).as_deref() { + Some("RENAME") => true, + Some("SET" | "UNSET") => !matches!( + self.text_at(0).as_deref(), + Some("ACCOUNT" | "PROFILE" | "SET" | "UNSET") + ), + _ => false, + }; + if named { let head = self.parse_alter_object_head()?; - let action = self.expect_alter_words(&[&["RENAME", "TO"]])?; - let parts: ActionParts = (vec![self.parse_group_name()?], vec![]); + let action = self.expect_alter_words(&[&["RENAME", "TO"], &["SET"], &["UNSET"]])?; + let parts: ActionParts = match action.as_str() { + "RENAME TO" => (vec![self.parse_group_name()?], vec![]), + "SET" => (vec![], self.parse_organization_set()?), + _ => (vec![], self.parse_group_unset_names()?), + }; return Ok(alter_object("ORGANIZATION", head, action, parts)); } if self.match_identifier("PROFILE") { diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_objects.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_objects.rs index 428920514..7e46d1dec 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_objects.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_objects.rs @@ -1,6 +1,7 @@ //! Snowflake ALTER of APPLICATION, APPLICATION PACKAGE, APPLICATION SERVICE, BACKUP SET, CORTEX SEARCH SERVICE, DATABASE, SEMANTIC VIEW and STREAMLIT. use crate::parser::_helpers::ddl::snowflake_alter::alter_object; +use crate::parser::_helpers::ddl::snowflake_alter::keyed_pair; use crate::parser::_helpers::ddl::snowflake_alter::named_property; use crate::parser::_helpers::ddl::snowflake_alter::parameter_property; use crate::parser::_helpers::ddl::snowflake_alter::property; @@ -167,7 +168,7 @@ impl Parser { loop { let item = self.parse_object_value()?; if self.match_token(TokenType::Eq) { - items.push(tuple(vec![item, self.parse_object_value()?])); + items.push(keyed_pair(item, self.parse_object_value()?, false)); } else { items.push(item); } @@ -491,12 +492,12 @@ impl Parser { "UPGRADE" => { if self.match_text_seq(&["USING", "VERSION"]) { action.push_str(" USING VERSION"); - let mut operands = vec![self.object_operand()?]; + let operands = vec![self.object_operand()?]; + let mut properties: Vec = Vec::new(); if self.match_text_seq(&["PATCH"]) { - action.push_str(" PATCH"); - operands.push(self.number_operand()?); + properties.push(property("PATCH", None, Some(self.number_operand()?))); } - (operands, vec![]) + (operands, properties) } else if self.match_text_seq(&["USING"]) { action.push_str(" USING"); (vec![self.parse_string_value()?], vec![]) diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_resources.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_resources.rs index 0f019e28f..4bc903a91 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_resources.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_resources.rs @@ -1,6 +1,7 @@ //! Parser ddl: Snowflake ALTER of schemas, stages, file formats, routines, services and project objects. use crate::parser::_helpers::ddl::snowflake_alter::alter_object; +use crate::parser::_helpers::ddl::snowflake_alter::keyed_pair; use crate::parser::_helpers::ddl::snowflake_alter::named_property; use crate::parser::_helpers::ddl::snowflake_alter::parameter_property; use crate::parser::_helpers::ddl::snowflake_alter::property; @@ -228,9 +229,12 @@ impl Parser { self.expect_token(TokenType::LParen)?; self.expect_token(TokenType::RParen)?; self.expect_token(TokenType::Returns)?; - self.parse_data_type()?; + let returns = self.parse_data_type()?; self.expect_token(TokenType::Arrow)?; - Ok(property("MAX_ROWS", None, Some(self.parse_expression()?))) + Ok(ObjectProperty { + returns: Some(returns), + ..property("MAX_ROWS", None, Some(self.parse_expression()?)) + }) } /// Whether `MODEL MONITOR` here starts `ALTER MODEL ` with a name or schema called monitor: a dot or a whole ALTER MODEL action follows. @@ -474,22 +478,20 @@ impl Parser { /// An external consumer's token action after its keyword: `{ PROGRAMMATIC ACCESS TOKEN | PAT } `, then MODIFY's `SET ...` or ADD and ROTATE's properties, with no IF [ NOT ] EXISTS. fn parse_consumer_token(&mut self, mut action: String) -> Result<(String, ActionParts)> { - if !self.match_text_seq(&["PROGRAMMATIC", "ACCESS", "TOKEN"]) - && !self.match_identifier("PAT") - { - return Err(self.parse_error("Expected PROGRAMMATIC ACCESS TOKEN or PAT")); - } - action.push_str(" PROGRAMMATIC ACCESS TOKEN"); + let verb = action.clone(); + let kind = self.expect_alter_words(&[&["PROGRAMMATIC", "ACCESS", "TOKEN"], &["PAT"]])?; + action.push(' '); + action.push_str(&kind); let operands = vec![self.parse_name_operand()?]; - let properties = match action.as_str() { - "MODIFY PROGRAMMATIC ACCESS TOKEN" => { + let properties = match verb.as_str() { + "MODIFY" => { if !self.match_token(TokenType::Set) { return Err(self.parse_error("Expected SET")); } action.push_str(" SET"); self.parse_resource_set()? } - "REMOVE PROGRAMMATIC ACCESS TOKEN" => Vec::new(), + "REMOVE" => Vec::new(), _ => self.parse_resource_properties()?, }; Ok((action, (operands, properties))) @@ -616,8 +618,14 @@ impl Parser { let mut properties: Vec = Vec::new(); match action.as_str() { "STOP ALL" if self.match_text_seq(&["OF", "TYPE"]) => { - action.push_str(" OF TYPE"); - operands = self.parse_object_name_list()?; + let types = Tuple { + expressions: self.parse_object_name_list()?, + }; + properties.push(property( + "OF TYPE", + None, + Some(Expression::Tuple(Box::new(types))), + )); } "EXECUTE AS" => { let caller = @@ -872,15 +880,12 @@ impl Parser { let mut items: Vec = Vec::new(); while !self.match_token(TokenType::RParen) { let item = self.parse_resource_value()?; - items.push( - if self.match_token(TokenType::Eq) || self.match_token(TokenType::FArrow) { - Expression::Tuple(Box::new(Tuple { - expressions: vec![item, self.parse_resource_value()?], - })) - } else { - item - }, - ); + let arrow = self.match_token(TokenType::FArrow); + items.push(if arrow || self.match_token(TokenType::Eq) { + keyed_pair(item, self.parse_resource_value()?, arrow) + } else { + item + }); if self.match_token(TokenType::Comma) && self.check(TokenType::RParen) { return Err(self.parse_error("Expected a value after the comma")); } diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_user.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_user.rs index 212a7d350..98de40e4e 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_user.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_alter_user.rs @@ -112,13 +112,9 @@ impl Parser { return Ok((self.parse_user_delegation(&verb, action)?, existence)); } let kind = self.expect_alter_words(CREDENTIALS)?; - let kind = match kind.as_str() { - "PAT" => "PROGRAMMATIC ACCESS TOKEN".to_string(), - _ => kind, - }; action.push(' '); action.push_str(&kind); - let token = matches!(kind.as_str(), "PROGRAMMATIC ACCESS TOKEN"); + let token = matches!(kind.as_str(), "PROGRAMMATIC ACCESS TOKEN" | "PAT"); if token && matches!(verb.as_str(), "ADD") && self.check(TokenType::If) { self.expect_alter_words(&[&["IF", "NOT", "EXISTS"]])?; existence.1 = true; diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_clone.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_clone.rs index 79202aa7b..734bb1915 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_clone.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_clone.rs @@ -339,7 +339,7 @@ impl Parser { })))) } - /// `[ LOCAL | GLOBAL ] { TEMP | TEMPORARY } READ ONLY TABLE [ IF NOT EXISTS ] CLONE ...` after its temporary words, which stay in the modifier as written; None for anything else. + /// `[ LOCAL | GLOBAL ] { TEMP | TEMPORARY } READ ONLY TABLE [ IF NOT EXISTS ] CLONE ...` after its temporary words, which stay in the modifier as written, with READ ONLY as its first property; None for anything else. pub(crate) fn parse_snowflake_read_only_clone( &mut self, or_replace: bool, @@ -370,10 +370,11 @@ impl Parser { if let Some(time_travel) = self.parse_clone_time_travel()? { operands.push(time_travel); } - let properties = self.parse_clone_options(CloneOptions::Copy)?; + let mut properties = vec![property("READ ONLY", None, None)]; + properties.extend(self.parse_clone_options(CloneOptions::Copy)?); Ok(Some(Expression::CreateObject(Box::new(CreateObject { kind: "TABLE".to_owned(), - modifier: Some(format!("{} READ ONLY", self.written_temporary(start))), + modifier: Some(self.written_temporary(start)), or_replace, if_not_exists, name, @@ -433,12 +434,11 @@ impl Parser { self.expect_token(TokenType::As)?; self.expect_alter_words(&[&["REPLICA", "OF"]])?; let primary = self.parse_object_name()?; - if primary.catalog.is_none() - || primary - .schema - .as_ref() - .is_none_or(|part| part.name.is_empty()) - { + let account_missing = primary + .schema + .as_ref() + .is_none_or(|part| part.name.is_empty()); + if primary.catalog.is_none() || (account_missing && !primary.schema_omitted) { return Err(self.parse_error("Expected ..")); } let mut properties: Vec = Vec::new(); diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_from.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_from.rs index 72284e369..fd866fc5f 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_from.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_from.rs @@ -2,6 +2,7 @@ use crate::parser::_helpers::ddl::snowflake_alter::property; use crate::parser::_helpers::ddl::snowflake_create_properties::with_written; +use crate::parser::constants::PLAIN_TEMPORARY; use crate::parser::models::Parser; use sqlquack_syntax::expressions::names::models::TableRef; use sqlquack_syntax::expressions::operators::models::BinaryOp; @@ -138,7 +139,7 @@ impl Parser { let version = Expression::Identifier(self.parse_simple_name()?); properties.push(property("VERSION", None, Some(version))); } - "MODEL" + "FROM MODEL" } "GATEWAY" if !self.check(TokenType::From) => { return Err(self.parse_error("Expected FROM SPECIFICATION")); @@ -150,7 +151,7 @@ impl Parser { return Err(self.parse_error("Expected the specification text")); } operands.push(self.parse_primary()?); - "SPECIFICATION" + "FROM SPECIFICATION" } _ if self.check(TokenType::Type) || self.check_identifier("TYPE") => return Ok(None), _ => "", @@ -172,8 +173,23 @@ impl Parser { })))) } + /// Record on a Snowflake temporary table's tree how TEMPORARY was spelled, when not plainly, from the words before `at`. + pub(crate) fn keep_temporary_spelling( + &self, + parsed: &mut Result, + temporary: bool, + at: usize, + ) { + if let (true, Ok(Expression::CreateTable(table))) = + (temporary && self.is_snowflake(), parsed) + { + let written = self.written_temporary(at); + table.temporary_spelling = (written != PLAIN_TEMPORARY).then_some(written); + } + } + /// The words before the token at `at` that made a CREATE temporary, as written and upper-cased: `[ LOCAL | GLOBAL ] { TEMP | TEMPORARY | VOLATILE }`. - pub(super) fn written_temporary(&self, at: usize) -> String { + pub(crate) fn written_temporary(&self, at: usize) -> String { let word = |offset: usize| { at.checked_sub(offset) .and_then(|index| self.tokens.get(index)) @@ -182,7 +198,7 @@ impl Parser { }; let temporary = word(1); if !matches!(temporary.as_str(), "TEMP" | "TEMPORARY" | "VOLATILE") { - return "TEMPORARY".to_owned(); + return PLAIN_TEMPORARY.to_owned(); } match word(2).as_str() { scope @ ("LOCAL" | "GLOBAL") => format!("{scope} {temporary}"), diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_openflow.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_openflow.rs index af37e43be..470d435dc 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_openflow.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_openflow.rs @@ -75,11 +75,13 @@ impl Parser { } let name = self.parse_object_name()?; let mut operands: Vec = Vec::new(); + let mut properties: Vec = Vec::new(); let mut source = String::new(); match kind { "CONNECTOR" => { self.expect_openflow_words(&["IN", "RUNTIME"])?; - operands.push(Expression::Table(Box::new(self.parse_object_name()?))); + let runtime = Expression::Table(Box::new(self.parse_object_name()?)); + properties.push(property("IN RUNTIME", None, Some(runtime))); self.expect_token(TokenType::From)?; if self.match_text_seq(&["DEFINITION"]) { source = "FROM DEFINITION".to_owned(); @@ -91,11 +93,11 @@ impl Parser { } "RUNTIME" => { self.expect_openflow_words(&["IN", "DEPLOYMENT"])?; - operands.push(Expression::Table(Box::new(self.parse_object_name()?))); + let deployment = Expression::Table(Box::new(self.parse_object_name()?)); + properties.push(property("IN DEPLOYMENT", None, Some(deployment))); } _ => {} } - let mut properties: Vec = Vec::new(); while !self.is_statement_end_at(0) { let key = self.peek_text().to_ascii_uppercase(); let Some(&(_, value)) = keys.iter().find(|(name, _)| *name == key) else { diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_properties.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_properties.rs index 1edfa0e6f..6757740ef 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_properties.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_create_properties.rs @@ -1,6 +1,7 @@ //! Snowflake CREATE for kinds defined only by ` = ` lists: users, policies, integrations and groups. use crate::parser::_helpers::ddl::snowflake_alter::property; +use crate::parser::_helpers::ddl::snowflake_alter::value_phrase; use crate::parser::constants::{ SNOWFLAKE_BODY_POLICY_KINDS, SNOWFLAKE_GROUP_NAME_PROPERTIES, SNOWFLAKE_KIND_WORDS_ALONE, SNOWFLAKE_PROPERTY_KINDS, SNOWFLAKE_STRING_KEY_MAP_PROPERTIES, SNOWFLAKE_TAGS_LAST_KINDS, @@ -38,12 +39,14 @@ impl Parser { return Err(self.parse_error("Expected an object kind after CREATE")); } let database_role = or_alter && self.names_a_database_role(); + let organization = self.names_an_organization(); let Some(parts) = SNOWFLAKE_PROPERTY_KINDS .iter() .map(|kind| kind.split(' ').collect::>()) - .filter(|parts| self.check_text_seq(parts)) + .filter(|parts| !organization && self.check_text_seq(parts)) .max_by_key(Vec::len) .or_else(|| database_role.then(|| vec!["DATABASE", "ROLE"])) + .or_else(|| organization.then(|| vec!["ORGANIZATION"])) else { let body = SNOWFLAKE_BODY_POLICY_KINDS .iter() @@ -90,6 +93,9 @@ impl Parser { with = true; continue; } + if parts == ["ORGANIZATION"] && !self.check_next(TokenType::Eq) { + return Err(self.parse_error("Expected = ")); + } let tags = self.check_identifier("TAG") && self.check_next(TokenType::LParen); if tags && or_alter && parts == ["SECRET"] { return Err(self.parse_error("CREATE OR ALTER and DEFINE SECRET take no tags")); @@ -329,6 +335,23 @@ impl Parser { })))) } + /// Whether CREATE names an organization itself: `ORGANIZATION `, where a kind word such as ACCOUNT or USER is the name when the statement ends after it or a property follows it. + fn names_an_organization(&self) -> bool { + if self.peek().token_type == TokenType::QuotedIdentifier + || !self.check_identifier("ORGANIZATION") + { + return false; + } + if !matches!( + self.text_at(1).as_deref(), + Some("ACCOUNT" | "LISTING" | "PROFILE" | "USER") + ) { + return true; + } + let at = |offset: usize| self.tokens.get(self.current + offset).map(|t| t.token_type); + matches!(at(2), None | Some(TokenType::Semicolon)) || at(3) == Some(TokenType::Eq) + } + /// Whether the words at the cursor are part of a property or body kind, not all of it, before a name and then `;`, AS, WITH, FROM or ` =`. fn names_a_partial_kind(&self) -> bool { let kinds = SNOWFLAKE_PROPERTY_KINDS @@ -554,31 +577,31 @@ impl Parser { let word = self.advance_text()?.to_ascii_uppercase(); return Ok(Expression::Identifier(Identifier::new(word))); } - let mut first = self.parse_object_value()?; - if let Expression::Table(table) = &mut first { - while self.continues_value_words() { - let word = self.advance_text()?; - table.name.name = format!("{} {}", table.name.name, word); - } - } + let first = self.parse_value_phrase()?; if in_list || !self.continues_name_list() { return Ok(first); } let mut expressions = vec![first]; while self.continues_name_list() { self.skip(); - let mut next = self.parse_object_value()?; - if let Expression::Table(table) = &mut next { - while self.continues_value_words() { - let word = self.advance_text()?; - table.name.name = format!("{} {}", table.name.name, word); - } - } - expressions.push(next); + expressions.push(self.parse_value_phrase()?); } Ok(Expression::Tuple(Box::new(Tuple { expressions }))) } + /// A value, or a keyword phrase such as SECURITY INTEGRATIONS when plain words follow a name. + fn parse_value_phrase(&mut self) -> Result { + let value = self.parse_object_value()?; + let Expression::Table(name) = value else { + return Ok(value); + }; + let mut more: Vec = Vec::new(); + while self.continues_value_words() { + more.push(self.advance_text()?); + } + Ok(value_phrase(name, more)) + } + /// Whether the next word continues a multi-word value, such as SECURITY INTEGRATIONS: a plain word that starts no ` =` or clause. fn continues_value_words(&self) -> bool { self.is_identifier_token() diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_data_metric.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_data_metric.rs index 76f0e8d21..b2b3b284d 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_data_metric.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_data_metric.rs @@ -146,7 +146,7 @@ impl Parser { if !self.match_token(TokenType::On) { return Err(self.parse_error("Expected ON after a data metric function name")); } - let arguments = self.parse_data_metric_arguments()?; + let (arguments, column_omitted) = self.parse_data_metric_arguments()?; let mut clauses: Vec = Vec::new(); match kind { "ADD" => { @@ -160,12 +160,13 @@ impl Parser { Ok(DataMetricFunctionItem { name, arguments, + column_omitted, clauses, }) } - /// `( [ , ... ] [ , TABLE ( ( , ... ) ) | , ] )`; the column list may be empty. - fn parse_data_metric_arguments(&mut self) -> Result> { + /// `( [ , ... ] [ , TABLE ( ( , ... ) ) | , ] )`, whose column list may be empty; returns the arguments and whether a leading comma left the column out. + fn parse_data_metric_arguments(&mut self) -> Result<(Vec, bool)> { self.expect_token(TokenType::LParen)?; let leading_comma = self.match_token(TokenType::Comma); let arguments = if self.check(TokenType::RParen) && !leading_comma { @@ -195,7 +196,7 @@ impl Parser { "A data metric function takes columns, then optionally TABLE ( ( , ... ) ) or a lambda", )); } - Ok(arguments) + Ok((arguments, leading_comma)) } /// A clause of ADD DATA METRIC FUNCTION, or none. diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_execute_jobs.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_execute_jobs.rs index 7adba5049..5dc511776 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_execute_jobs.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_execute_jobs.rs @@ -1,7 +1,10 @@ //! Snowflake `EXECUTE JOB SERVICE`, `INFERENCE JOB SERVICE`, `NOTEBOOK [ PROJECT ]`, `DBT PROJECT` and `CODE BUNDLE`. +use crate::parser::_helpers::ddl::snowflake_alter::keyed_pair; use crate::parser::_helpers::ddl::snowflake_alter::property; use crate::parser::models::Parser; +use sqlquack_syntax::expressions::operators::models::NamedArgSeparator; +use sqlquack_syntax::expressions::operators::models::NamedArgument; use sqlquack_syntax::expressions::schema_objects::models::ExecuteObject; use sqlquack_syntax::expressions::schema_objects::models::ObjectProperty; use sqlquack_syntax::expressions::tree::models::Expression; @@ -311,9 +314,7 @@ impl Parser { let key = self.parse_execute_value()?; self.expect_token(TokenType::Eq)?; let value = self.parse_execute_value()?; - Ok(Expression::Tuple(Box::new(Tuple { - expressions: vec![key, value], - }))) + Ok(keyed_pair(key, value, false)) } /// An IMPORTS entry: a location or a system function call, with an optional `AS ''`. @@ -336,11 +337,12 @@ impl Parser { self.expect_token(TokenType::LParen)?; let mut items: Vec = Vec::new(); loop { - let name = Expression::Identifier(self.parse_simple_name()?); + let name = self.parse_simple_name()?; self.expect_token(TokenType::FArrow)?; - let value = self.parse_expression()?; - items.push(Expression::Tuple(Box::new(Tuple { - expressions: vec![name, value], + items.push(Expression::NamedArgument(Box::new(NamedArgument { + name, + value: self.parse_expression()?, + separator: NamedArgSeparator::DArrow, }))); if !self.match_token(TokenType::Comma) { break; diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_opaque.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_opaque.rs index a5bcc7154..8790ee8fe 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_opaque.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/ddl/snowflake_opaque.rs @@ -863,7 +863,7 @@ impl Parser { ) } else if create.kind == MODEL_KIND && self.match_text_seq(&[MODEL_KIND]) { ( - "MODEL", + "FROM MODEL", Expression::Table(Box::new(self.parse_object_name()?)), ) } else if self.is_stage_path() || self.check(TokenType::String) { diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/expressions/operators.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/expressions/operators.rs index 9aabadb61..4bab44b87 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/expressions/operators.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/expressions/operators.rs @@ -519,6 +519,7 @@ impl Parser { &mut self, temporary: bool, or_replace: bool, + or_alter: bool, ) -> Result { self.expect_token(TokenType::Sequence)?; @@ -535,31 +536,15 @@ impl Parser { if_not_exists, temporary, or_replace, - as_type: None, - increment: None, - minvalue: None, - maxvalue: None, - start: None, - cache: None, - cycle: false, - owned_by: None, - owned_by_none: false, - order: None, - comment: None, - sharing: None, - scale_modifier: None, - shard_modifier: None, - property_order: Vec::new(), - define: false, - comment_name: None, - start_text: None, + or_alter, + ..CreateSequence::new(String::new()) }; if self.match_token(TokenType::As) { seq.as_type = Some(self.parse_data_type()?); } - self.match_token(TokenType::With); + seq.with = self.match_token(TokenType::With); loop { self.match_token(TokenType::Comma); @@ -656,8 +641,8 @@ impl Parser { } fn parse_sequence_start_clause(&mut self, mut seq: CreateSequence) -> Result { - self.match_token(TokenType::With); - self.match_token(TokenType::Eq); + seq.spelling.start_with = self.match_token(TokenType::With); + seq.spelling.start_eq = self.match_token(TokenType::Eq); match self.parse_sequence_start()? { Ok(start) => seq.start = Some(start), Err(text) => seq.start_text = Some(text), @@ -683,8 +668,8 @@ impl Parser { } fn parse_sequence_increment(&mut self, mut seq: CreateSequence) -> Result { - self.match_token(TokenType::By); - self.match_token(TokenType::Eq); + seq.spelling.increment_by = self.match_token(TokenType::By); + seq.spelling.increment_eq = self.match_token(TokenType::Eq); seq.increment = Some(self.parse_signed_integer()?); seq.property_order.push(SeqPropKind::Increment); Ok(seq) diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/queries/clauses.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/queries/clauses.rs index f4e6e0758..58662268e 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/queries/clauses.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/queries/clauses.rs @@ -1449,8 +1449,10 @@ impl Parser { let mut expr = self.parse_expression()?; if self.match_token(TokenType::As) { - let alias = self.parse_unquoted_identifier(Self::expect_identifier)?; - expr = Expression::Alias(Box::new(Alias::new(expr, alias))); + let alias = self.parse_quoting_identifier(Self::expect_identifier)?; + let mut alias = Alias::new(expr, alias); + alias.alias_explicit_as = true; + expr = Expression::Alias(Box::new(alias)); } else if self.is_snowflake() { let at_clause = self.is_at_end() || self.check(TokenType::Comma) @@ -1595,22 +1597,22 @@ impl Parser { } if self.match_token(TokenType::First) { - let name = self.expect_identifier()?; - return Ok(Some(MatchRecognizeAfter::ToFirst(Identifier::new(name)))); + let name = self.parse_quoting_identifier(Self::expect_identifier)?; + return Ok(Some(MatchRecognizeAfter::ToFirst(name))); } if self.match_token(TokenType::Last) { - let name = self.expect_identifier()?; - return Ok(Some(MatchRecognizeAfter::ToLast(Identifier::new(name)))); + let name = self.parse_quoting_identifier(Self::expect_identifier)?; + return Ok(Some(MatchRecognizeAfter::ToLast(name))); } if self.is_snowflake() { let name = if next { - self.advance_text()? + self.parse_quoting_identifier(Self::advance_text)? } else { - self.expect_identifier()? + self.parse_quoting_identifier(Self::expect_identifier)? }; - return Ok(Some(MatchRecognizeAfter::To(Identifier::new(name)))); + return Ok(Some(MatchRecognizeAfter::To(name))); } return Err(self.parse_error("Expected NEXT ROW, FIRST x, or LAST x after TO")); @@ -1686,7 +1688,7 @@ impl Parser { let mut semantics: Vec<(usize, MatchRecognizeSemantics)> = Vec::new(); loop { - let name = self.parse_unquoted_identifier(Self::expect_identifier)?; + let name = self.parse_quoting_identifier(Self::expect_identifier)?; self.expect_token(TokenType::As)?; if self.is_snowflake() { semantics.extend( diff --git a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/statements/dml.rs b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/statements/dml.rs index 4f62c025b..56ab0b08e 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/_helpers/statements/dml.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/_helpers/statements/dml.rs @@ -476,8 +476,9 @@ impl Parser { return table; } } - let parsed = + let mut parsed = self.parse_create_table(or_replace, temporary, leading_comments, modifier); + self.keep_temporary_spelling(&mut parsed, temporary, table_start); let checkable = !define && !secure && matches!(modifier, None | Some("TRANSIENT" | "HYBRID")); self.keep_snowflake_create_table( @@ -541,7 +542,7 @@ impl Parser { .parse_create_procedure(or_replace, or_alter) .map(|procedure| with_routine_modifiers(procedure, secure, temporary, false)), }, - TokenType::Sequence => self.parse_create_sequence(temporary, or_replace), + TokenType::Sequence => self.parse_create_sequence(temporary, or_replace, or_alter), TokenType::Trigger => { self.parse_create_trigger(or_replace, or_alter, false, create_pos) } diff --git a/crates/sqlquack-syntax-snowflake/src/parser/constants.rs b/crates/sqlquack-syntax-snowflake/src/parser/constants.rs index 1f4be2437..6e6486f70 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/constants.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/constants.rs @@ -584,6 +584,9 @@ pub(crate) const MODEL_KIND: &str = "MODEL"; /// The opaque CREATE kinds that take their manifest only after AS, never FROM a location: a bare LISTING, unlike an EXTERNAL or ORGANIZATION LISTING. pub(crate) const SNOWFLAKE_MANIFEST_ONLY_KINDS: &[&str] = &["LISTING"]; +/// The plain spelling of a temporary CREATE, which a tree records as its flag alone. +pub(crate) const PLAIN_TEMPORARY: &str = "TEMPORARY"; + /// The modifiers an opaque CREATE may write before its kind, each kept as written. pub(crate) const SNOWFLAKE_OPAQUE_MODIFIERS: &[&[&str]] = &[ &["LOCAL", "TEMPORARY"], @@ -1071,6 +1074,7 @@ pub(crate) const SNOWFLAKE_UNDROP_KINDS: &[&str] = &[ "TAG", "TYPE", "VARIABLE", + "VOLUME", ]; /// The UNDROP kinds that take a system-generated id in `IDENTIFIER( )`, as Snowflake's UNDROP pages name them. diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_alter.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_alter.rs index 023d59078..36a86eda1 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_alter.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_alter.rs @@ -293,6 +293,24 @@ fn given_a_snowflake_alter_of_an_object_kind_when_parsing_then_recorded_rejectio dialect: Dialect::Snowflake, expected_accepted: false, }, + AcceptanceTestCase { + description: "FORCE before an account policy's FOR ALL scope is refused (recorded errno 1003)", + sql: "ALTER ACCOUNT SET MAINTENANCE POLICY v1 FORCE FOR ALL APPLICATIONS ;", + dialect: Dialect::Snowflake, + expected_accepted: false, + }, + AcceptanceTestCase { + description: "FORCE after an account policy's FOR ALL scope is accepted (recorded semantic error)", + sql: "ALTER ACCOUNT SET FEATURE POLICY v1 FOR ALL APPLICATIONS FORCE ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "FORCE without a scope is accepted (recorded semantic error)", + sql: "ALTER ACCOUNT SET PACKAGES POLICY v1 FORCE ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, AcceptanceTestCase { description: "only external tables add partitions (recorded errno 1003)", sql: "ALTER TABLE v1 ADD PARTITION ( v1 = 'm' ) LOCATION 'm' ;", @@ -567,6 +585,114 @@ fn given_a_snowflake_alter_of_an_object_kind_when_parsing_then_the_tree_names_ki pointer: "/0/alter_object/kind", expected_value: json!("COMPUTE POOL"), }, + ProjectionTestCase { + description: "a parenthesised KEY = value keeps the pair keyed (recorded semantic error)", + sql: "ALTER USER v1 SET WORKLOAD_IDENTITY = ( ANOMALY_DETECTION = TRUE ) ;", + pointer: "/0/alter_object/properties/0/value/tuple/expressions/0/property_e_q/left/table/name/name", + expected_value: json!("ANOMALY_DETECTION"), + }, + ProjectionTestCase { + description: "a keyed pair's value is its right side", + sql: "ALTER USER v1 SET WORKLOAD_IDENTITY = ( ANOMALY_DETECTION = TRUE ) ;", + pointer: "/0/alter_object/properties/0/value/tuple/expressions/0/property_e_q/right/boolean/value", + expected_value: json!(true), + }, + ProjectionTestCase { + description: "a string key keeps its pair keyed", + sql: "ALTER STREAMLIT v1 SET SECRETS = ( 'm' = v1 , 'n' = v2 ) ;", + pointer: "/0/alter_object/properties/0/value/tuple/expressions/1/property_e_q/left/literal/value", + expected_value: json!("n"), + }, + ProjectionTestCase { + description: "a nested option list keeps its pairs keyed", + sql: "ALTER EXTERNAL VOLUME v1 ADD STORAGE_LOCATION = ( NAME = 'm' STORAGE_PROVIDER = 'S3' ENCRYPTION = ( TYPE = 'AWS_SSE_S3' ) ) ;", + pointer: "/0/alter_object/operands/0/tuple/expressions/2/property_e_q/right/tuple/expressions/0/property_e_q/left/table/name/name", + expected_value: json!("TYPE"), + }, + ProjectionTestCase { + description: "=> keeps its separator as a named argument", + sql: "ALTER SERVICE v1 FROM @s1 SPECIFICATION_TEMPLATE_FILE = 'm' USING ( v1 => 1 ) ;", + pointer: "/0/alter_object/properties/1/value/tuple/expressions/0/named_argument/separator", + expected_value: json!("DArrow"), + }, + ProjectionTestCase { + description: "a policy's FOR ALL scope is its own property (recorded semantic error)", + sql: "ALTER ACCOUNT SET AUTHENTICATION POLICY v1 FOR ALL PERSON USERS FORCE ;", + pointer: "/0/alter_object/properties/0/kind", + expected_value: json!("FOR ALL PERSON USERS"), + }, + ProjectionTestCase { + description: "the policy's action names only the policy", + sql: "ALTER ACCOUNT SET AUTHENTICATION POLICY v1 FOR ALL PERSON USERS FORCE ;", + pointer: "/0/alter_object/action", + expected_value: json!("SET AUTHENTICATION POLICY"), + }, + ProjectionTestCase { + description: "FORCE after the scope follows it", + sql: "ALTER ACCOUNT SET AUTHENTICATION POLICY v1 FOR ALL SERVICE USERS FORCE ;", + pointer: "/0/alter_object/properties/1/kind", + expected_value: json!("FORCE"), + }, + ProjectionTestCase { + description: "an unset policy keeps its scope", + sql: "ALTER ACCOUNT UNSET AUTHENTICATION POLICY FOR ALL PERSON USERS ;", + pointer: "/0/alter_object/properties/0/kind", + expected_value: json!("FOR ALL PERSON USERS"), + }, + ProjectionTestCase { + description: "a user's PAT keeps its short spelling (recorded success)", + sql: "ALTER USER v1 MODIFY PAT v1 UNSET DISABLED ;", + pointer: "/0/alter_object/action", + expected_value: json!("MODIFY PAT UNSET"), + }, + ProjectionTestCase { + description: "a user's PROGRAMMATIC ACCESS TOKEN keeps its long spelling", + sql: "ALTER USER v1 MODIFY PROGRAMMATIC ACCESS TOKEN v1 UNSET DISABLED ;", + pointer: "/0/alter_object/action", + expected_value: json!("MODIFY PROGRAMMATIC ACCESS TOKEN UNSET"), + }, + ProjectionTestCase { + description: "a consumer's PAT keeps its short spelling", + sql: "ALTER EXTERNAL CONSUMER v1 REMOVE PAT v1 ;", + pointer: "/0/alter_object/action", + expected_value: json!("REMOVE PAT"), + }, + ProjectionTestCase { + description: "a consumer's modified PAT still takes SET", + sql: "ALTER EXTERNAL CONSUMER v1 MODIFY PAT v1 SET COMMENT = 'm' ;", + pointer: "/0/alter_object/action", + expected_value: json!("MODIFY PAT SET"), + }, + ProjectionTestCase { + description: "STOP ALL keeps OF TYPE as a property (recorded semantic error)", + sql: "ALTER COMPUTE POOL v1 STOP ALL OF TYPE USER , USER ;", + pointer: "/0/alter_object/properties/0/kind", + expected_value: json!("OF TYPE"), + }, + ProjectionTestCase { + description: "OF TYPE keeps each workload type", + sql: "ALTER COMPUTE POOL v1 STOP ALL OF TYPE USER , SYSTEM ;", + pointer: "/0/alter_object/properties/0/value/tuple/expressions/1/table/name/name", + expected_value: json!("SYSTEM"), + }, + ProjectionTestCase { + description: "STOP ALL OF TYPE names only STOP ALL as its action", + sql: "ALTER COMPUTE POOL v1 STOP ALL OF TYPE USER ;", + pointer: "/0/alter_object/action", + expected_value: json!("STOP ALL"), + }, + ProjectionTestCase { + description: "UPGRADE USING VERSION keeps PATCH as a property (recorded semantic error)", + sql: "ALTER APPLICATION v1 UPGRADE USING VERSION v1 PATCH 1 ;", + pointer: "/0/alter_object/properties/0/value/literal/value", + expected_value: json!("1"), + }, + ProjectionTestCase { + description: "UPGRADE USING VERSION with PATCH names only USING VERSION as its action", + sql: "ALTER APPLICATION v1 UPGRADE USING VERSION v1 PATCH 1 ;", + pointer: "/0/alter_object/action", + expected_value: json!("UPGRADE USING VERSION"), + }, ProjectionTestCase { description: "IF EXISTS is kept", sql: "ALTER TAG IF EXISTS v1 RENAME TO v2", diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_alter_gaps.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_alter_gaps.rs index 27c0d73a8..f376e5832 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_alter_gaps.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_alter_gaps.rs @@ -184,7 +184,7 @@ fn given_snowflake_table_actions_and_method_calls_when_parsing_then_the_tree_kee description: "ALTER USER without a name acts on the current user", sql: "ALTER USER ADD PAT t1 DAYS_TO_EXPIRY = 1", pointer: "/0/alter_object/action", - expected_value: json!("ADD PROGRAMMATIC ACCESS TOKEN"), + expected_value: json!("ADD PAT"), }, ProjectionTestCase { description: "a token's IF NOT EXISTS is a flag, not part of the action", @@ -196,7 +196,7 @@ fn given_snowflake_table_actions_and_method_calls_when_parsing_then_the_tree_kee description: "a token's IF EXISTS is a flag apart from the user's", sql: "ALTER USER IF EXISTS u1 MODIFY PAT IF EXISTS t1 RENAME TO t2", pointer: "/0/alter_object/action", - expected_value: json!("MODIFY PROGRAMMATIC ACCESS TOKEN RENAME TO"), + expected_value: json!("MODIFY PAT RENAME TO"), }, ProjectionTestCase { description: "the user's IF EXISTS stays on the user", @@ -223,10 +223,35 @@ fn given_snowflake_table_actions_and_method_calls_when_parsing_then_the_tree_kee expected_value: json!("ICEBERG TABLE"), }, ProjectionTestCase { - description: "IF NOT EXISTS is part of the version tag action", + description: "IF NOT EXISTS on the version tag is a flag, not part of the action", sql: "ALTER ICEBERG TABLE v1 CREATE VERSION_TAG IF NOT EXISTS t1", pointer: "/0/alter_object/action", - expected_value: json!("CREATE VERSION_TAG IF NOT EXISTS"), + expected_value: json!("CREATE VERSION_TAG"), + }, + ProjectionTestCase { + description: "IF NOT EXISTS on the version tag is kept", + sql: "ALTER ICEBERG TABLE v1 CREATE VERSION_TAG IF NOT EXISTS t1", + pointer: "/0/alter_object/operand_if_not_exists", + expected_value: json!(true), + }, + ProjectionTestCase { + description: + "IF NOT EXISTS on a scoring profile is kept as a flag (recorded semantic error)", + sql: "ALTER CORTEX SEARCH SERVICE v1 ADD SCORING PROFILE IF NOT EXISTS v1 '{}' ;", + pointer: "/0/alter_object/operand_if_not_exists", + expected_value: json!(true), + }, + ProjectionTestCase { + description: "IF EXISTS on a dropped scoring profile is kept as a flag", + sql: "ALTER CORTEX SEARCH SERVICE v1 DROP SCORING PROFILE IF EXISTS v1 ;", + pointer: "/0/alter_object/operand_if_exists", + expected_value: json!(true), + }, + ProjectionTestCase { + description: "IF NOT EXISTS on an organization profile version is kept as a flag", + sql: "ALTER ORGANIZATION PROFILE v1 ADD VERSION IF NOT EXISTS v1 FROM @d1.s1.st1/p1 ;", + pointer: "/0/alter_object/action", + expected_value: json!("ADD VERSION FROM"), }, ProjectionTestCase { description: "RETAIN keeps its unit", @@ -309,6 +334,11 @@ fn given_snowflake_policy_and_integration_alters_without_a_name_when_parsing_the AcceptanceTestCase { description: "an integration named refresh refreshes its secret", sql: "ALTER INTEGRATION refresh REFRESH OAUTH_CLIENT_SECRET ;", dialect: Dialect::Snowflake, expected_accepted: true }, AcceptanceTestCase { description: "an organization user's visibility may be another value", sql: "ALTER ORGANIZATION USER v1 SET VISIBILITY = 'x' ;", dialect: Dialect::Snowflake, expected_accepted: true }, AcceptanceTestCase { description: "VISIBILITY = ALL is followed by other properties", sql: "ALTER ORGANIZATION USER v1 SET VISIBILITY = ALL , COMMENT = 'm' ;", dialect: Dialect::Snowflake, expected_accepted: true }, + AcceptanceTestCase { description: "a named organization sets a property (recorded semantic error)", sql: "ALTER ORGANIZATION v1 SET COMMENT = 'm' ;", dialect: Dialect::Snowflake, expected_accepted: true }, + AcceptanceTestCase { description: "a named organization unsets a property (recorded semantic error)", sql: "ALTER ORGANIZATION v1 UNSET EMAIL ;", dialect: Dialect::Snowflake, expected_accepted: true }, + AcceptanceTestCase { description: "a named organization's SET names a property (recorded syntax error)", sql: "ALTER ORGANIZATION v1 SET ;", dialect: Dialect::Snowflake, expected_accepted: false }, + AcceptanceTestCase { description: "a named organization's UNSET names a property (recorded syntax error)", sql: "ALTER ORGANIZATION v1 UNSET ;", dialect: Dialect::Snowflake, expected_accepted: false }, + AcceptanceTestCase { description: "a named organization takes no COMMIT (recorded syntax error)", sql: "ALTER ORGANIZATION v1 COMMIT ;", dialect: Dialect::Snowflake, expected_accepted: false }, ]; for test_case in test_cases { @@ -367,6 +397,18 @@ fn given_snowflake_policy_and_integration_alters_without_a_name_when_parsing_the pointer: "/0/alter_object/properties/0/kind", expected_value: json!("VISIBILITY"), }, + ProjectionTestCase { + description: "a named organization keeps its name", + sql: "ALTER ORGANIZATION v1 UNSET EMAIL", + pointer: "/0/alter_object/name/name/name", + expected_value: json!("v1"), + }, + ProjectionTestCase { + description: "a named organization's SET keeps its property", + sql: "ALTER ORGANIZATION v1 SET COMMENT = 'm'", + pointer: "/0/alter_object/properties/0/kind", + expected_value: json!("COMMENT"), + }, ]; for test_case in test_cases { diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_alert.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_alert.rs index f9f31fc7b..ee282d50d 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_alert.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_alert.rs @@ -117,7 +117,7 @@ fn given_a_snowflake_alert_or_data_movement_rule_when_parsing_then_only_what_sno } #[test] -fn given_a_snowflake_alert_create_when_parsing_then_the_tree_keeps_its_parts() { +fn given_a_snowflake_alert_or_data_movement_rule_when_parsing_then_the_tree_keeps_its_parts() { let test_cases = [ ProjectionTestCase { description: "the condition is the first operand", @@ -131,6 +131,18 @@ fn given_a_snowflake_alert_create_when_parsing_then_the_tree_keeps_its_parts() { pointer: "/0/create_object/properties/0/kind", expected_value: json!("SUSPENDED"), }, + ProjectionTestCase { + description: "a data movement rule's row limit keeps its return type", + sql: "CREATE DATA MOVEMENT RULE v1 TYPE = 'm' MAX_ROWS AS ( ) RETURNS INTEGER -> ( 1 ) ;", + pointer: "/0/create_object/properties/1/returns/data_type", + expected_value: json!("int"), + }, + ProjectionTestCase { + description: "an altered row limit keeps its return type", + sql: "ALTER DATA MOVEMENT RULE IF EXISTS v1 SET MAX_ROWS AS ( ) RETURNS VARCHAR -> ( 1 ) ;", + pointer: "/0/alter_object/properties/0/returns/data_type", + expected_value: json!("var_char"), + }, ]; for test_case in test_cases { assert_eq!( diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_clone.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_clone.rs index a0821e21f..dff8c2409 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_clone.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_clone.rs @@ -153,9 +153,33 @@ fn given_a_snowflake_clone_or_replica_when_parsing_then_only_documented_forms_ar expected_accepted: true, }, AcceptanceTestCase { - description: "a replica's source has all three parts", + description: "a replica's source may leave out its account (recorded semantic error)", sql: "CREATE FAILOVER GROUP v1 AS REPLICA OF v1 . . v1 ;", dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "a connection replica may leave out its account (recorded unsupported feature)", + sql: "CREATE CONNECTION v1 AS REPLICA OF v1 . . v1 ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "a replica's source needs its organization (recorded syntax error)", + sql: "CREATE REPLICATION GROUP v1 AS REPLICA OF . v1 . v1 ;", + dialect: Dialect::Snowflake, + expected_accepted: false, + }, + AcceptanceTestCase { + description: "a replica's source needs its name (recorded syntax error)", + sql: "CREATE FAILOVER GROUP v1 AS REPLICA OF v1 . v1 . ;", + dialect: Dialect::Snowflake, + expected_accepted: false, + }, + AcceptanceTestCase { + description: "a group replica's source has three parts", + sql: "CREATE REPLICATION GROUP v1 AS REPLICA OF v1 . v1 ;", + dialect: Dialect::Snowflake, expected_accepted: false, }, AcceptanceTestCase { @@ -233,6 +257,18 @@ fn given_a_snowflake_clone_or_replica_when_parsing_then_the_tree_keeps_its_parts pointer: "/0/create_object/operands/0/table/catalog/name", expected_value: json!("o1"), }, + ProjectionTestCase { + description: "a replica's left-out account is null, not an empty name", + sql: "CREATE REPLICATION GROUP g1 AS REPLICA OF o1 . . g2", + pointer: "/0/create_object/operands/0/table/schema", + expected_value: json!(null), + }, + ProjectionTestCase { + description: "a replica records that its account was left out", + sql: "CREATE REPLICATION GROUP g1 AS REPLICA OF o1 . . g2", + pointer: "/0/create_object/operands/0/table/schema_omitted", + expected_value: json!(true), + }, ProjectionTestCase { description: "a failover group replica names its source", sql: "CREATE FAILOVER GROUP g1 AS REPLICA OF o1.a1.g2", diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_properties.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_properties.rs index 23e97339d..8df62eeed 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_properties.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_create_properties.rs @@ -367,9 +367,9 @@ fn given_a_snowflake_property_or_policy_create_when_parsing_then_the_tree_keeps_ expected_value: json!("DEFAULT_ROLE"), }, ProjectionTestCase { - description: "a multi-word value stays one value", - sql: "CREATE FAILOVER GROUP g1 OBJECT_TYPES = INTEGRATIONS ALLOWED_INTEGRATION_TYPES = SECURITY INTEGRATIONS ALLOWED_ACCOUNTS = o1.a1", - pointer: "/0/create_object/properties/1/value/table/name/name", + description: "a multi-word value stays one keyword phrase, not a name (recorded success)", + sql: "CREATE FAILOVER GROUP g1 OBJECT_TYPES = INTEGRATIONS ALLOWED_INTEGRATION_TYPES = security INTEGRATIONS ALLOWED_ACCOUNTS = o1.a1", + pointer: "/0/create_object/properties/1/value/var/this", expected_value: json!("SECURITY INTEGRATIONS"), }, ProjectionTestCase { @@ -390,6 +390,36 @@ fn given_a_snowflake_property_or_policy_create_when_parsing_then_the_tree_keeps_ pointer: "/0/create_object/source", expected_value: json!("SHARE"), }, + ProjectionTestCase { + description: "an organization named ACCOUNT is an organization, not an account", + sql: "CREATE ORGANIZATION ACCOUNT ADMIN_NAME = 'm' EMAIL = 'm'", + pointer: "/0/create_object/kind", + expected_value: json!("ORGANIZATION"), + }, + ProjectionTestCase { + description: "an organization named ACCOUNT keeps the word as its name", + sql: "CREATE ORGANIZATION ACCOUNT ADMIN_NAME = 'm' EMAIL = 'm'", + pointer: "/0/create_object/name/name/name", + expected_value: json!("ACCOUNT"), + }, + ProjectionTestCase { + description: "an organization account keeps its own kind", + sql: "CREATE ORGANIZATION ACCOUNT v1 ADMIN_NAME = 'm' EMAIL = 'm'", + pointer: "/0/create_object/kind", + expected_value: json!("ORGANIZATION ACCOUNT"), + }, + ProjectionTestCase { + description: "a one-word value stays a name", + sql: "CREATE FAILOVER GROUP g1 OBJECT_TYPES = INTEGRATIONS ALLOWED_INTEGRATION_TYPES = SECURITY INTEGRATIONS ALLOWED_ACCOUNTS = o1.a1", + pointer: "/0/create_object/properties/0/value/table/name/name", + expected_value: json!("INTEGRATIONS"), + }, + ProjectionTestCase { + description: "an altered group's multi-word value is a keyword phrase too", + sql: "ALTER FAILOVER GROUP g1 SET ALLOWED_INTEGRATION_TYPES = SECURITY INTEGRATIONS , API INTEGRATIONS", + pointer: "/0/alter_object/properties/0/value/tuple/expressions/1/var/this", + expected_value: json!("API INTEGRATIONS"), + }, ProjectionTestCase { description: "a warehouse keeps OR ALTER", sql: "CREATE OR ALTER WAREHOUSE v1 WAREHOUSE_SIZE = MEDIUM", @@ -523,10 +553,40 @@ fn given_a_snowflake_property_or_policy_create_when_parsing_then_the_tree_keeps_ expected_value: json!("SCOPED TEMP"), }, ProjectionTestCase { - description: "a read-only temporary clone keeps its words", + description: "a read-only temporary clone keeps its temporary words as its modifier", sql: "CREATE OR REPLACE LOCAL TEMP READ ONLY TABLE v1 CLONE v2 COPY GRANTS", pointer: "/0/create_object/modifier", - expected_value: json!("LOCAL TEMP READ ONLY"), + expected_value: json!("LOCAL TEMP"), + }, + ProjectionTestCase { + description: "a read-only temporary clone keeps READ ONLY as a property", + sql: "CREATE OR REPLACE LOCAL TEMP READ ONLY TABLE v1 CLONE v2 COPY GRANTS", + pointer: "/0/create_object/properties/0/kind", + expected_value: json!("READ ONLY"), + }, + ProjectionTestCase { + description: "a volatile table clone keeps VOLATILE (recorded success)", + sql: "CREATE VOLATILE TABLE v1 CLONE v1 ;", + pointer: "/0/create_table/temporary_spelling", + expected_value: json!("VOLATILE"), + }, + ProjectionTestCase { + description: "a global temporary table keeps GLOBAL TEMPORARY", + sql: "CREATE GLOBAL TEMPORARY TABLE t1 ( c1 INT ) ;", + pointer: "/0/create_table/temporary_spelling", + expected_value: json!("GLOBAL TEMPORARY"), + }, + ProjectionTestCase { + description: "a TEMP table keeps TEMP", + sql: "CREATE TEMP TABLE v1 CLONE v1 ;", + pointer: "/0/create_table/temporary_spelling", + expected_value: json!("TEMP"), + }, + ProjectionTestCase { + description: "a TEMPORARY table needs no spelling", + sql: "CREATE TEMPORARY TABLE t1 ( c1 INT ) ;", + pointer: "/0/create_table/temporary_spelling", + expected_value: json!(null), }, ProjectionTestCase { description: "a class instance keeps its class's omitted schema as null", @@ -583,10 +643,16 @@ fn given_a_snowflake_property_or_policy_create_when_parsing_then_the_tree_keeps_ expected_value: json!("PIPE"), }, ProjectionTestCase { - description: "a model named monitor copied from a model keeps its MODEL source", + description: "a model named monitor copied from a model keeps its FROM MODEL source", sql: "CREATE MODEL monitor FROM MODEL m2", pointer: "/0/create_object/source", - expected_value: json!("MODEL"), + expected_value: json!("FROM MODEL"), + }, + ProjectionTestCase { + description: "an experiment keeps FROM in its specification source (recorded runtime error)", + sql: "CREATE EXPERIMENT v1 FROM SPECIFICATION $$m1 $$ ;", + pointer: "/0/create_object/source", + expected_value: json!("FROM SPECIFICATION"), }, ProjectionTestCase { description: "a dropped account keeps its grace period", @@ -732,6 +798,66 @@ fn given_a_snowflake_create_without_a_complete_kind_when_parsing_then_it_is_refu dialect: Dialect::Snowflake, expected_accepted: true, }, + AcceptanceTestCase { + description: "an organization named v1 takes properties (recorded semantic error)", + sql: "CREATE ORGANIZATION v1 ADMIN_NAME = 'm' ADMIN_PASSWORD = 'm' EMAIL = 'm' EDITION = ENTERPRISE ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "an organization named ACCOUNT takes properties (recorded semantic error)", + sql: "CREATE ORGANIZATION ACCOUNT ADMIN_NAME = 'm' ADMIN_PASSWORD = 'm' EMAIL = 'm' EDITION = ENTERPRISE ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "an organization named ACCOUNT reads v1 as a property (recorded semantic error)", + sql: "CREATE ORGANIZATION ACCOUNT v1 = 'm' ADMIN_PASSWORD = 'm' EMAIL = 'm' EDITION = ENTERPRISE ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "an organization named v1 alone (recorded semantic error)", + sql: "CREATE ORGANIZATION v1 ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "an organization named PROFILE alone (recorded semantic error)", + sql: "CREATE ORGANIZATION PROFILE ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "an organization named USER alone (recorded semantic error)", + sql: "CREATE ORGANIZATION USER ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "a reserved word is no organization profile name (recorded syntax error)", + sql: "CREATE ORGANIZATION PROFILE SELECT ;", + dialect: Dialect::Snowflake, + expected_accepted: false, + }, + AcceptanceTestCase { + description: "a reserved word is no organization account name (recorded syntax error)", + sql: "CREATE ORGANIZATION ACCOUNT SELECT ADMIN_NAME = 'm' ADMIN_PASSWORD = 'm' EMAIL = 'm' EDITION = ENTERPRISE ;", + dialect: Dialect::Snowflake, + expected_accepted: false, + }, + AcceptanceTestCase { + description: "a nameless organization profile has no AS (recorded syntax error)", + sql: "CREATE ORGANIZATION PROFILE AS 'm' ;", + dialect: Dialect::Snowflake, + expected_accepted: false, + }, + AcceptanceTestCase { + description: "an organization named v1 takes no AS (recorded syntax error)", + sql: "CREATE ORGANIZATION v1 AS 'm' ;", + dialect: Dialect::Snowflake, + expected_accepted: false, + }, AcceptanceTestCase { description: "a packages policy is kept", sql: "CREATE PACKAGES POLICY v1 LANGUAGE PYTHON ;", diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_execute.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_execute.rs index c55aac4aa..e2a4d76e2 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_execute.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_execute.rs @@ -83,7 +83,8 @@ fn given_a_snowflake_execute_of_a_job_service_when_parsing_then_the_tree_keeps_i ProjectionTestCase { description: "the USING arguments keep their name", sql: SQL, - pointer: "/0/execute_object/properties/3/value/tuple/expressions/0/tuple/expressions/0/identifier/name", + pointer: + "/0/execute_object/properties/3/value/tuple/expressions/0/named_argument/name/name", expected_value: json!("k"), }, ProjectionTestCase { diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_openflow.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_openflow.rs index 84ff5a412..d99a9d946 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_openflow.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_openflow.rs @@ -176,13 +176,13 @@ fn given_a_snowflake_create_openflow_when_parsing_then_the_tree_keeps_its_parts( ProjectionTestCase { description: "a connector keeps its runtime", sql: "CREATE OPENFLOW CONNECTOR c1 IN RUNTIME r1 FROM DEFINITION d1", - pointer: "/0/create_object/operands/0/table/name/name", + pointer: "/0/create_object/properties/0/value/table/name/name", expected_value: json!("r1"), }, ProjectionTestCase { description: "a connector keeps its definition", sql: "CREATE OPENFLOW CONNECTOR c1 IN RUNTIME r1 FROM DEFINITION d1", - pointer: "/0/create_object/operands/1/table/name/name", + pointer: "/0/create_object/operands/0/table/name/name", expected_value: json!("d1"), }, ProjectionTestCase { @@ -194,33 +194,45 @@ fn given_a_snowflake_create_openflow_when_parsing_then_the_tree_keeps_its_parts( ProjectionTestCase { description: "a connector keeps its stage location as a string", sql: "CREATE OPENFLOW CONNECTOR c1 IN RUNTIME r1 FROM '@s1/p1'", - pointer: "/0/create_object/operands/1/literal/value", + pointer: "/0/create_object/operands/0/literal/value", expected_value: json!("@s1/p1"), }, ProjectionTestCase { description: "a runtime keeps its deployment", sql: "CREATE OPENFLOW RUNTIME r1 IN DEPLOYMENT p1 NODE_TYPE = SMALL MIN_NODES = 1 MAX_NODES = 2 EXECUTE_AS_ROLE = o1", - pointer: "/0/create_object/operands/0/table/name/name", + pointer: "/0/create_object/properties/0/value/table/name/name", expected_value: json!("p1"), }, ProjectionTestCase { description: "a node type is a keyword, not a name", sql: "CREATE OPENFLOW RUNTIME r1 IN DEPLOYMENT p1 NODE_TYPE = small MIN_NODES = 1 MAX_NODES = 2 EXECUTE_AS_ROLE = o1", - pointer: "/0/create_object/properties/0/value/var/this", + pointer: "/0/create_object/properties/1/value/var/this", expected_value: json!("SMALL"), }, ProjectionTestCase { description: "a runtime keeps its role as a name", sql: "CREATE OPENFLOW RUNTIME r1 IN DEPLOYMENT p1 NODE_TYPE = SMALL MIN_NODES = 1 MAX_NODES = 2 EXECUTE_AS_ROLE = o1", - pointer: "/0/create_object/properties/3/value/table/name/name", + pointer: "/0/create_object/properties/4/value/table/name/name", expected_value: json!("o1"), }, ProjectionTestCase { description: "a runtime's integrations stay one list", sql: "CREATE OPENFLOW RUNTIME r1 IN DEPLOYMENT p1 NODE_TYPE = SMALL MIN_NODES = 1 MAX_NODES = 2 EXECUTE_AS_ROLE = o1 EXTERNAL_ACCESS_INTEGRATIONS = ( i1 , i2 )", - pointer: "/0/create_object/properties/4/value/tuple/expressions/1/table/name/name", + pointer: "/0/create_object/properties/5/value/tuple/expressions/1/table/name/name", expected_value: json!("i2"), }, + ProjectionTestCase { + description: "a runtime labels its deployment with IN DEPLOYMENT (recorded semantic error)", + sql: "CREATE OPENFLOW RUNTIME r1 IN DEPLOYMENT p1 NODE_TYPE = SMALL MIN_NODES = 1 MAX_NODES = 2 EXECUTE_AS_ROLE = o1", + pointer: "/0/create_object/properties/0/kind", + expected_value: json!("IN DEPLOYMENT"), + }, + ProjectionTestCase { + description: "a connector labels its runtime with IN RUNTIME", + sql: "CREATE OPENFLOW CONNECTOR c1 IN RUNTIME r1 FROM DEFINITION d1", + pointer: "/0/create_object/properties/0/kind", + expected_value: json!("IN RUNTIME"), + }, ProjectionTestCase { description: "a deployment type is a keyword", sql: "CREATE OPENFLOW DEPLOYMENT p1 DEPLOYMENT_TYPE = byoc", diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_receipted_objects.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_receipted_objects.rs index 2d8f66916..f376c88b7 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_receipted_objects.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_receipted_objects.rs @@ -144,7 +144,7 @@ fn given_a_snowflake_external_consumer_when_parsing_then_the_tree_keeps_its_kind description: "a token action keeps its words", sql: "ALTER EXTERNAL CONSUMER v1 ROTATE PAT v1 ;", pointer: "/0/alter_object/action", - expected_value: json!("ROTATE PROGRAMMATIC ACCESS TOKEN"), + expected_value: json!("ROTATE PAT"), }, ]; for test_case in test_cases { diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_recorded.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_recorded.rs index e6ca1b556..0df8fed1c 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_recorded.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_recorded.rs @@ -32,6 +32,18 @@ fn given_recorded_snowflake_cases_when_parsing_then_acceptance_matches_snowflake dialect: Dialect::Snowflake, expected_accepted: true, }, + AcceptanceTestCase { + description: "UNDROP takes VOLUME without EXTERNAL (recorded semantic error)", + sql: "UNDROP VOLUME v1 ;", + dialect: Dialect::Snowflake, + expected_accepted: true, + }, + AcceptanceTestCase { + description: "UNDROP EXTERNAL VOLUME needs its name (recorded syntax error)", + sql: "UNDROP EXTERNAL VOLUME ;", + dialect: Dialect::Snowflake, + expected_accepted: false, + }, AcceptanceTestCase { description: "UNDROP DATABASE's IDENTIFIER( ) takes no bare word (recorded errno 1003)", sql: "UNDROP DATABASE IDENTIFIER ( m1 ) ;", @@ -941,6 +953,48 @@ fn given_snowflake_create_and_begin_name_when_parsing_then_the_tree_keeps_the_el pointer: "/0/create_data_metric_function/nullable", expected_value: json!(false), }, + ProjectionTestCase { + description: "a data metric function's arguments keep a left-out column (recorded semantic error)", + sql: "ALTER TABLE t1 ADD DATA METRIC FUNCTION SNOWFLAKE . CORE . ACCEPTED_VALUES ON ( , x -> x > 0 ) ;", + pointer: "/0/alter_table/actions/0/DataMetricFunction/items/0/column_omitted", + expected_value: json!(true), + }, + ProjectionTestCase { + description: "a left-out column before TABLE is kept", + sql: "ALTER TABLE v1 ADD DATA METRIC FUNCTION SNOWFLAKE . CORE . ROW_COUNT_DIFFERENCE ON ( , TABLE ( v1 ( v1 ) ) ) ;", + pointer: "/0/alter_table/actions/0/DataMetricFunction/items/0/column_omitted", + expected_value: json!(true), + }, + ProjectionTestCase { + description: "a data metric function's written column leaves nothing out", + sql: "ALTER TABLE t1 ADD DATA METRIC FUNCTION SNOWFLAKE . CORE . ACCEPTED_VALUES ON ( v1 , x -> x > 0 ) ;", + pointer: "/0/alter_table/actions/0/DataMetricFunction/items/0/column_omitted", + expected_value: json!(null), + }, + ProjectionTestCase { + description: "a runtime version written as a name says so (recorded semantic error)", + sql: "CREATE FUNCTION v1 ( ) RETURNS INT LANGUAGE PYTHON RUNTIME_VERSION = m1 PACKAGES = ( 'm' ) HANDLER = 'm' AS 'm'", + pointer: "/0/create_function/runtime_version_bare", + expected_value: json!("Name"), + }, + ProjectionTestCase { + description: "a runtime version written as a number says so", + sql: "CREATE FUNCTION v1 ( ) RETURNS INT LANGUAGE PYTHON RUNTIME_VERSION = 3 PACKAGES = ( 'm' ) HANDLER = 'm' AS 'm'", + pointer: "/0/create_function/runtime_version_bare", + expected_value: json!("Number"), + }, + ProjectionTestCase { + description: "a runtime version written as a string is no bare value", + sql: "CREATE FUNCTION v1 ( ) RETURNS INT LANGUAGE PYTHON RUNTIME_VERSION = '3.11' PACKAGES = ( 'm' ) HANDLER = 'm' AS 'm'", + pointer: "/0/create_function/runtime_version_bare", + expected_value: json!(null), + }, + ProjectionTestCase { + description: "a package written as a name says so by position (recorded semantic error)", + sql: "CREATE FUNCTION v1 ( ) RETURNS INT LANGUAGE PYTHON RUNTIME_VERSION = '3.11' PACKAGES = ( 'm' , m1 ) HANDLER = 'm' AS 'm'", + pointer: "/0/create_function/packages_bare", + expected_value: json!([[1, "Name"]]), + }, ProjectionTestCase { description: "a typed literal keeps its type's name and parameters", sql: "SELECT PERIOD ( INT ) 'x'", diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_sequences.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_sequences.rs index 01c4c65e8..fa46c340b 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_sequences.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_sequences.rs @@ -1,7 +1,9 @@ -use sqlquack_syntax::Dialect; +use serde_json::json; -use crate::helpers::{accepted, snowflake_kind}; -use crate::test_types::{AcceptanceTestCase, SnowflakeVerdictTestCase}; +use sqlquack_syntax::{CallMode, Dialect}; + +use crate::helpers::{accepted, snowflake_kind, statements}; +use crate::test_types::{AcceptanceTestCase, ProjectionTestCase, SnowflakeVerdictTestCase}; #[test] fn given_a_snowflake_sequence_when_parsing_then_recorded_verdicts_hold() { @@ -83,3 +85,56 @@ fn given_another_dialect_when_altering_a_sequence_increment_then_only_by_is_acce ); } } + +#[test] +fn given_a_snowflake_sequence_create_when_parsing_then_the_tree_keeps_its_written_words() { + let test_cases = [ + ProjectionTestCase { + description: "OR ALTER is kept (recorded success)", + sql: "CREATE OR ALTER SEQUENCE v1 ;", + pointer: "/0/create_sequence/or_alter", + expected_value: json!(true), + }, + ProjectionTestCase { + description: "a plain create is no OR ALTER", + sql: "CREATE SEQUENCE v1 ;", + pointer: "/0/create_sequence/or_alter", + expected_value: json!(null), + }, + ProjectionTestCase { + description: "the leading WITH is kept", + sql: "CREATE SEQUENCE v1 WITH START WITH = 1 INCREMENT BY = 1 ;", + pointer: "/0/create_sequence/with", + expected_value: json!(true), + }, + ProjectionTestCase { + description: "START WITH = and INCREMENT BY = keep their optional words", + sql: "CREATE SEQUENCE v1 WITH START WITH = 1 INCREMENT BY = 1 ;", + pointer: "/0/create_sequence/spelling", + expected_value: json!({"start_with": true, "start_eq": true, "increment_by": true, "increment_eq": true}), + }, + ProjectionTestCase { + description: "START and INCREMENT alone leave the optional words out", + sql: "CREATE SEQUENCE v1 START 1 INCREMENT 1 ;", + pointer: "/0/create_sequence/spelling", + expected_value: json!(null), + }, + ProjectionTestCase { + description: "START = keeps its = without WITH", + sql: "CREATE SEQUENCE v1 START = 1 INCREMENT BY 1 ;", + pointer: "/0/create_sequence/spelling", + expected_value: json!({"start_eq": true, "increment_by": true}), + }, + ]; + for test_case in test_cases { + assert_eq!( + statements(test_case.sql, Dialect::Snowflake, CallMode::Faithful) + .pointer(test_case.pointer) + .cloned() + .unwrap_or_default(), + test_case.expected_value, + "{}", + test_case.description + ); + } +} diff --git a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_tables.rs b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_tables.rs index 645948872..51c8a3a57 100644 --- a/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_tables.rs +++ b/crates/sqlquack-syntax-snowflake/tests/parser/test_snowflake_tables.rs @@ -1128,6 +1128,42 @@ fn given_a_snowflake_clause_accepted_on_its_receipt_when_parsing_then_the_tree_k pointer: "/0/select/from/expressions/0/match_recognize/define_semantics", expected_value: json!([[1, "Final"]]), }, + StatementTestCase { + description: "SKIP TO keeps a quoted pattern variable's quoting", + sql: "SELECT * FROM t1 MATCH_RECOGNIZE ( ORDER BY c1 MEASURES COUNT ( * ) AS \"m Q\" , COUNT ( * ) m2 AFTER MATCH SKIP TO \"a Q\" PATTERN ( \"a Q\" ) DEFINE \"a Q\" AS TRUE ) ;", + pointer: "/0/select/from/expressions/0/match_recognize/after/To/quoted", + expected_value: json!(true), + }, + StatementTestCase { + description: "SKIP TO LAST keeps a quoted pattern variable's quoting", + sql: "SELECT * FROM t1 MATCH_RECOGNIZE ( ORDER BY c1 AFTER MATCH SKIP TO LAST \"SELECT\" PATTERN ( \"SELECT\" ) DEFINE \"SELECT\" AS TRUE ) ;", + pointer: "/0/select/from/expressions/0/match_recognize/after/ToLast/quoted", + expected_value: json!(true), + }, + StatementTestCase { + description: "a DEFINE name keeps its quoting", + sql: "SELECT * FROM t1 MATCH_RECOGNIZE ( ORDER BY c1 MEASURES COUNT ( * ) AS \"m Q\" , COUNT ( * ) m2 AFTER MATCH SKIP TO \"a Q\" PATTERN ( \"a Q\" ) DEFINE \"a Q\" AS TRUE ) ;", + pointer: "/0/select/from/expressions/0/match_recognize/define/0/0/quoted", + expected_value: json!(true), + }, + StatementTestCase { + description: "a measure alias after AS keeps its quoting", + sql: "SELECT * FROM t1 MATCH_RECOGNIZE ( ORDER BY c1 MEASURES COUNT ( * ) AS \"m Q\" , COUNT ( * ) m2 AFTER MATCH SKIP TO \"a Q\" PATTERN ( \"a Q\" ) DEFINE \"a Q\" AS TRUE ) ;", + pointer: "/0/select/from/expressions/0/match_recognize/measures/0/this/alias/alias/quoted", + expected_value: json!(true), + }, + StatementTestCase { + description: "a measure alias records its written AS", + sql: "SELECT * FROM t1 MATCH_RECOGNIZE ( ORDER BY c1 MEASURES COUNT ( * ) AS \"m Q\" , COUNT ( * ) m2 AFTER MATCH SKIP TO \"a Q\" PATTERN ( \"a Q\" ) DEFINE \"a Q\" AS TRUE ) ;", + pointer: "/0/select/from/expressions/0/match_recognize/measures/0/this/alias/alias_explicit_as", + expected_value: json!(true), + }, + StatementTestCase { + description: "a measure alias without AS records none", + sql: "SELECT * FROM t1 MATCH_RECOGNIZE ( ORDER BY c1 MEASURES COUNT ( * ) AS \"m Q\" , COUNT ( * ) m2 AFTER MATCH SKIP TO \"a Q\" PATTERN ( \"a Q\" ) DEFINE \"a Q\" AS TRUE ) ;", + pointer: "/0/select/from/expressions/0/match_recognize/measures/1/this/alias/alias_explicit_as", + expected_value: json!(false), + }, StatementTestCase { description: "SHOW PARAMETERS IN USER keeps USER as the scope kind", sql: "SHOW PARAMETERS IN USER \"v1 Q\" ;", diff --git a/crates/sqlquack-syntax/src/expressions/columns/models.rs b/crates/sqlquack-syntax/src/expressions/columns/models.rs index 5cb125d84..b4191da24 100644 --- a/crates/sqlquack-syntax/src/expressions/columns/models.rs +++ b/crates/sqlquack-syntax/src/expressions/columns/models.rs @@ -618,6 +618,9 @@ pub struct DataMetricColumn { pub struct DataMetricFunctionItem { pub name: TableRef, pub arguments: Vec, + /// `ON ( , )`: the column before the first comma was left out. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub column_omitted: bool, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub clauses: Vec, } diff --git a/crates/sqlquack-syntax/src/expressions/schema_objects/models.rs b/crates/sqlquack-syntax/src/expressions/schema_objects/models.rs index cf335bfc9..a58df467d 100644 --- a/crates/sqlquack-syntax/src/expressions/schema_objects/models.rs +++ b/crates/sqlquack-syntax/src/expressions/schema_objects/models.rs @@ -531,6 +531,12 @@ pub struct CreateFunction { /// Snowflake: PACKAGES=('pkg1', 'pkg2') #[serde(default, skip_serializing_if = "Option::is_none")] pub packages: Option>, + /// Snowflake: how RUNTIME_VERSION was written when not as a string + #[serde(default, skip_serializing_if = "Option::is_none")] + pub runtime_version_bare: Option, + /// Snowflake: how each PACKAGES entry not written as a string was written, by its position in `packages` + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub packages_bare: Vec<(usize, BareValue)>, /// PARAMETER STYLE clause (e.g., PANDAS for Databricks) #[serde(default, skip_serializing_if = "Option::is_none")] pub parameter_style: Option, @@ -708,6 +714,8 @@ impl CreateFunction { handler_uses_eq: false, runtime_version: None, packages: None, + runtime_version_bare: None, + packages_bare: Vec::new(), parameter_style: None, snowflake_properties: Vec::new(), overloads: Vec::new(), @@ -717,6 +725,13 @@ impl CreateFunction { } } +/// A value Snowflake usually takes as a string, written instead as a name or a number. +#[derive(sqlquack_derive::AstNode, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum BareValue { + Name, + Number, +} + /// DROP FUNCTION statement #[derive(sqlquack_derive::AstNode, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct DropFunction { @@ -927,6 +942,38 @@ pub struct CreateSequence { /// Snowflake: a START value outside the 64-bit range, as written; `start` is then None #[serde(default, skip_serializing_if = "Option::is_none")] pub start_text: Option, + /// Snowflake: written as `CREATE OR ALTER SEQUENCE` + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub or_alter: bool, + /// The optional WITH written before the properties + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub with: bool, + /// The optional words written in START [ WITH ] [ = ] and INCREMENT [ BY ] [ = ] + #[serde(default, skip_serializing_if = "SequenceSpelling::is_empty")] + pub spelling: SequenceSpelling, +} + +/// The optional words a sequence's START and INCREMENT were written with. +#[derive(sqlquack_derive::AstNode, Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct SequenceSpelling { + /// `START WITH` + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub start_with: bool, + /// `START [ WITH ] =` + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub start_eq: bool, + /// `INCREMENT BY` + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub increment_by: bool, + /// `INCREMENT [ BY ] =` + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub increment_eq: bool, +} + +impl SequenceSpelling { + pub fn is_empty(&self) -> bool { + *self == Self::default() + } } /// Sequence bound (value or NO MINVALUE/NO MAXVALUE) @@ -961,6 +1008,9 @@ impl CreateSequence { define: false, comment_name: None, start_text: None, + or_alter: false, + with: false, + spelling: SequenceSpelling::default(), } } } @@ -1352,6 +1402,9 @@ pub struct ObjectProperty { /// Whether the optional WITH was written before it, as in Snowflake's `WITH TAG ( ... )` #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub with: bool, + /// The type after RETURNS when the value is a body, as in `MAX_ROWS AS ( ) RETURNS -> ` + #[serde(default, skip_serializing_if = "Option::is_none")] + pub returns: Option, } /// Snowflake `EXECUTE ALERT | TASK [ GRAPH ] | DCM PROJECT ` with its action. diff --git a/crates/sqlquack-syntax/src/expressions/tables/models.rs b/crates/sqlquack-syntax/src/expressions/tables/models.rs index 86a497f26..fb110f508 100644 --- a/crates/sqlquack-syntax/src/expressions/tables/models.rs +++ b/crates/sqlquack-syntax/src/expressions/tables/models.rs @@ -176,6 +176,9 @@ pub struct CreateTable { /// WITH CONNECTION `project.region.connection`, as BigQuery external tables use. #[serde(default, skip_serializing_if = "Option::is_none")] pub with_connection: Option, + /// Snowflake: the words that made the table temporary when they were not TEMPORARY, e.g. `TEMP`, `VOLATILE` or `LOCAL TEMP` + #[serde(default, skip_serializing_if = "Option::is_none")] + pub temporary_spelling: Option, } /// Teradata index specification for CREATE TABLE @@ -246,6 +249,7 @@ impl CreateTable { uuid: None, with_partition_columns: Vec::new(), with_connection: None, + temporary_spelling: None, } } }