capabilities.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Package capabilities describes the SQL surface an engine build supports.
  2. //
  3. // The document is machine-readable and versioned separately from the engine
  4. // build. Clients should compare SchemaVersion when parsing it and use
  5. // EngineVersion to pin behavior to a specific PizzaSQL release. Keep this file
  6. // in sync with the tests it advertises: every claim here must be covered by an
  7. // engine test in the same repository.
  8. package capabilities
  9. import (
  10. "time"
  11. "github.com/danfragoso/pizzasql-next/pkg/version"
  12. )
  13. // SchemaVersion changes only when the document's shape or semantics change in a
  14. // way clients must handle. Additive fields do not require a bump.
  15. const SchemaVersion = 1
  16. // Feature reports whether a capability is supported and, when it is not,
  17. // whether the engine rejects the syntax or accepts it with different semantics.
  18. type Feature struct {
  19. Supported bool `json:"supported"`
  20. Status string `json:"status"`
  21. SQLState string `json:"sqlstate,omitempty"`
  22. Notes string `json:"notes,omitempty"`
  23. }
  24. // Document is the machine-readable capability description served by
  25. // GET /capabilities.
  26. type Document struct {
  27. SchemaVersion int `json:"schema_version"`
  28. Engine string `json:"engine"`
  29. EngineVersion string `json:"engine_version"`
  30. Commit string `json:"commit"`
  31. GeneratedAt string `json:"generated_at,omitempty"`
  32. Protocol Protocol `json:"protocol"`
  33. Notifications Notifications `json:"notifications"`
  34. Constraints Constraints `json:"constraints"`
  35. DDL DDL `json:"ddl"`
  36. Transactions Transactions `json:"transactions"`
  37. Types Types `json:"types"`
  38. Parameters Parameters `json:"parameters"`
  39. JSON JSON `json:"json"`
  40. ErrorCodes map[string]string `json:"error_codes"`
  41. Documentation Documentation `json:"documentation"`
  42. }
  43. type Protocol struct {
  44. PostgresCompat string `json:"postgres_compat"`
  45. SQLiteCompat string `json:"sqlite_compat"`
  46. SimpleQuery bool `json:"simple_query"`
  47. ExtendedQuery bool `json:"extended_query"`
  48. Describe Feature `json:"describe"`
  49. BinaryResults Feature `json:"binary_results"`
  50. BoundedExecute Feature `json:"bounded_execute"`
  51. ProxyErrors bool `json:"proxy_error_relay"`
  52. TLS string `json:"tls"`
  53. }
  54. type Notifications struct {
  55. Listen Feature `json:"listen"`
  56. Unlisten Feature `json:"unlisten"`
  57. Notify Feature `json:"notify"`
  58. PayloadLimit int `json:"payload_limit_bytes"`
  59. ChannelLimit int `json:"channel_limit_bytes"`
  60. Transactional bool `json:"transactional"`
  61. DurableReplay bool `json:"durable_replay"`
  62. CommitLSNParam string `json:"commit_lsn_parameter"`
  63. }
  64. type Constraints struct {
  65. NotNull Feature `json:"not_null"`
  66. Unique Feature `json:"unique"`
  67. PrimaryKey Feature `json:"primary_key"`
  68. CompositePK Feature `json:"composite_primary_key"`
  69. ForeignKey Feature `json:"foreign_key"`
  70. Check Feature `json:"check"`
  71. DefaultValues Feature `json:"default_values"`
  72. }
  73. type DDL struct {
  74. AddColumn Feature `json:"add_column"`
  75. AddColumnBackfill Feature `json:"add_column_backfill"`
  76. DropColumn Feature `json:"drop_column"`
  77. RenameColumn Feature `json:"rename_column"`
  78. RenameColumnIndexes Feature `json:"rename_column_updates_indexes"`
  79. CreateTableIfNotExists bool `json:"create_table_if_not_exists"`
  80. AddColumnIfNotExists bool `json:"add_column_if_not_exists"`
  81. }
  82. type Transactions struct {
  83. Model string `json:"model"`
  84. Isolation string `json:"isolation"`
  85. SerializationCode string `json:"serialization_sqlstate"`
  86. RowLocks Feature `json:"row_locks"`
  87. Savepoints Feature `json:"savepoints"`
  88. RetryRecommended bool `json:"retry_recommended"`
  89. }
  90. type Types struct {
  91. Integer bool `json:"integer"`
  92. Bigint bool `json:"bigint"`
  93. Real bool `json:"real"`
  94. Text bool `json:"text"`
  95. Blob bool `json:"blob"`
  96. Boolean bool `json:"boolean"`
  97. DateTime bool `json:"datetime"`
  98. JSONText bool `json:"json_as_text"`
  99. JSONB Feature `json:"jsonb"`
  100. Arrays Feature `json:"arrays"`
  101. RowID Feature `json:"rowid"`
  102. }
  103. type Parameters struct {
  104. Positional bool `json:"positional"`
  105. MaxOID string `json:"max_oid"`
  106. Arrays Feature `json:"arrays"`
  107. BinaryNumeric bool `json:"binary_numeric"`
  108. }
  109. type JSON struct {
  110. Storage string `json:"storage"`
  111. Functions string `json:"functions"`
  112. ArrowOperators Feature `json:"arrow_operators"`
  113. InvalidInput string `json:"invalid_input"`
  114. }
  115. type Documentation struct {
  116. Compatibility string `json:"compatibility"`
  117. Isolation string `json:"isolation"`
  118. }
  119. // Current returns the capability document for this build.
  120. func Current() *Document {
  121. return &Document{
  122. SchemaVersion: SchemaVersion,
  123. Engine: "pizzasql",
  124. EngineVersion: version.String(),
  125. Commit: version.Commit,
  126. GeneratedAt: time.Now().UTC().Format(time.RFC3339),
  127. Protocol: Protocol{
  128. PostgresCompat: "14.0",
  129. SQLiteCompat: "3.35.0",
  130. SimpleQuery: true,
  131. ExtendedQuery: true,
  132. Describe: Feature{Supported: true, Status: "compatibility_no_data", Notes: "statement Describe returns ParameterDescription then NoData; portal Describe returns NoData; Execute still emits the real RowDescription"},
  133. BinaryResults: Feature{Supported: false, Status: "rejected", SQLState: "0A000", Notes: "all-text result formats only; per-column format lists are also rejected"},
  134. BoundedExecute: Feature{Supported: false, Status: "rejected", SQLState: "0A000", Notes: "Execute maxRows must be zero; portals are one-shot, not resumable"},
  135. ProxyErrors: true,
  136. TLS: "terminated by the managed pgproxy, not the engine listener",
  137. },
  138. Notifications: Notifications{
  139. Listen: Feature{Supported: true, Status: "enforced"},
  140. Unlisten: Feature{Supported: true, Status: "enforced", Notes: "UNLISTEN * removes every subscription"},
  141. Notify: Feature{Supported: true, Status: "enforced", Notes: "duplicate (channel,payload) pairs collapse within one transaction"},
  142. PayloadLimit: 8000,
  143. ChannelLimit: 63,
  144. Transactional: true,
  145. DurableReplay: false,
  146. CommitLSNParam: "pizzasql.lsn",
  147. },
  148. Constraints: Constraints{
  149. NotNull: Feature{Supported: true, Status: "enforced", SQLState: "23502", Notes: "validated after defaults and assignments; explicit NULL does not select a default"},
  150. Unique: Feature{Supported: true, Status: "enforced", SQLState: "23505", Notes: "unique indexes, INLINE UNIQUE, and ON CONFLICT are enforced atomically"},
  151. PrimaryKey: Feature{Supported: true, Status: "enforced", SQLState: "23505", Notes: "single-column only"},
  152. CompositePK: Feature{Supported: false, Status: "rejected", SQLState: "0A000", Notes: "declare a single-column primary key"},
  153. ForeignKey: Feature{Supported: false, Status: "rejected", SQLState: "0A000", Notes: "inline and table-level FOREIGN KEY are rejected; enforce referential integrity in the application, cascades are not implemented"},
  154. Check: Feature{Supported: false, Status: "rejected", SQLState: "0A000", Notes: "inline and table-level CHECK are rejected; enforce the rule in the application"},
  155. DefaultValues: Feature{Supported: true, Status: "constant_only", SQLState: "0A000", Notes: "non-constant defaults (CURRENT_TIMESTAMP, function calls) are rejected instead of being frozen at DDL time"},
  156. },
  157. DDL: DDL{
  158. AddColumn: Feature{Supported: true, Status: "metadata_only"},
  159. AddColumnBackfill: Feature{Supported: false, Status: "no_backfill", Notes: "existing rows read NULL; a DEFAULT applies to later INSERTs only"},
  160. DropColumn: Feature{Supported: true, Status: "rewrites_rows"},
  161. RenameColumn: Feature{Supported: true, Status: "rewrites_rows"},
  162. RenameColumnIndexes: Feature{Supported: false, Status: "stale_index_metadata", Notes: "drop and recreate indexes that reference a renamed column"},
  163. CreateTableIfNotExists: true,
  164. AddColumnIfNotExists: true,
  165. },
  166. Transactions: Transactions{
  167. Model: "optimistic compare-batch commit; no MVCC or snapshots",
  168. Isolation: "serializable-equivalent for single-row compare-batch commits; write skew is possible across independent rows",
  169. SerializationCode: "40001",
  170. RowLocks: Feature{Supported: false, Status: "rejected", SQLState: "0A000", Notes: "SELECT ... FOR UPDATE/SHARE and NOWAIT/SKIP LOCKED are rejected"},
  171. Savepoints: Feature{Supported: true, Status: "enforced"},
  172. RetryRecommended: true,
  173. },
  174. Types: Types{
  175. Integer: true,
  176. Bigint: true,
  177. Real: true,
  178. Text: true,
  179. Blob: true,
  180. Boolean: true,
  181. DateTime: true,
  182. JSONText: true,
  183. JSONB: Feature{Supported: false, Status: "type_name_alias", Notes: "JSONB is accepted as a column type name with no native semantics; values are stored as text and reported as TEXT over the wire"},
  184. Arrays: Feature{Supported: false, Status: "rejected", SQLState: "0A000"},
  185. RowID: Feature{Supported: true, Status: "auto_assigned", Notes: "rowid/oid/_rowid_ are BIGINT over the wire; monotonic when the engine assigns the rowid, stable across UPDATE, not monotonic for client-assigned descending INTEGER PRIMARY KEY values"},
  186. },
  187. Parameters: Parameters{
  188. Positional: true,
  189. MaxOID: "int8/text/bool/bytea/float; arrays are rejected",
  190. Arrays: Feature{Supported: false, Status: "rejected", SQLState: "0A000", Notes: "id = ANY($1) is rejected; expand to IN (...) with individual parameters"},
  191. BinaryNumeric: true,
  192. },
  193. JSON: JSON{
  194. Storage: "TEXT (JSON and JSONB type names are accepted without native semantics; insert JSON as text)",
  195. Functions: "json, json_extract, json_set, json_insert, json_replace, json_remove, json_valid, json_type, json_array, json_array_length, json_object, json_quote, json_group_array; PostgreSQL text compatibility: concat_ws, string_agg, pg_backend_pid",
  196. ArrowOperators: Feature{Supported: false, Status: "rejected", SQLState: "42601", Notes: "-> and ->> are not implemented; use json_extract"},
  197. InvalidInput: "json_extract/json_set throw 'malformed JSON'; json_valid returns 0",
  198. },
  199. ErrorCodes: map[string]string{
  200. "23502": "not_null_violation",
  201. "23505": "unique_violation",
  202. "40001": "serialization_failure",
  203. "0A000": "feature_not_supported",
  204. "42501": "insufficient_privilege (managed proxy)",
  205. "42601": "syntax_error",
  206. "25P02": "in_failed_sql_transaction",
  207. "08P01": "protocol_violation (managed proxy: TLS required)",
  208. "53300": "too_many_connections (managed proxy: organization quota)",
  209. "XX000": "internal_error",
  210. },
  211. Documentation: Documentation{
  212. Compatibility: "https://docs.database.pizza/sql-reference/compatibility/",
  213. Isolation: "https://docs.database.pizza/engine/concurrency/",
  214. },
  215. }
  216. }