jade-sql 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  require 'jade-sql/bin/generate_schema'
2
+ require 'jade-sql/schema_drift'
2
3
 
3
4
  namespace :jade do
4
5
  desc "Generate schema.jd from db/structure.sql (INPUT, OUTPUT, TABLES, COLUMNS, MODULE)"
@@ -9,15 +10,47 @@ namespace :jade do
9
10
  columns = parse_columns_env(ENV['COLUMNS'])
10
11
  module_name = ENV['MODULE'] || 'Schema'
11
12
 
12
- FileUtils.mkdir_p(File.dirname(output))
13
- File.write(
14
- output,
15
- JadeSql::SchemaGenerator.generate(
16
- File.read(input), tables:, columns:, module_name:
17
- ),
18
- )
13
+ JadeSql::SchemaGenerator
14
+ .generate(File.read(input), tables:, columns:, module_name:)
15
+ .map { |name, source| write_module(output, module_name, name, source) }
16
+ .then { puts "wrote #{it.join(', ')}" }
17
+ end
18
+
19
+ # Every module the generator would write, read back if it is there. A file
20
+ # left behind for a module no longer generated is not found this way, which
21
+ # is the lesser problem: a missing one breaks the build, a stale one is dead
22
+ # code the compiler still reads.
23
+ def on_disk_modules(generated, root_module, output)
24
+ generated.keys.to_h do |name|
25
+ JadeSql::SchemaGenerator
26
+ .module_path(root_module, name, output)
27
+ .then { [name, File.exist?(it) ? File.read(it) : nil] }
28
+ end
29
+ end
30
+
31
+ def write_module(output, root_module, name, source)
32
+ JadeSql::SchemaGenerator
33
+ .module_path(root_module, name, output)
34
+ .tap { FileUtils.mkdir_p(File.dirname(it)) }
35
+ .tap { File.write(it, source) }
36
+ end
37
+
38
+ namespace :schema do
39
+ desc "Fail if schema.jd no longer matches db/structure.sql (INPUT, OUTPUT, TABLES, COLUMNS, MODULE)"
40
+ task :check do
41
+ input = ENV['INPUT'] || 'db/structure.sql'
42
+ output = ENV['OUTPUT'] || 'app/jade/schema.jd'
43
+ tables = ENV['TABLES']&.split(',')&.map(&:strip)&.reject(&:empty?)
44
+ columns = parse_columns_env(ENV['COLUMNS'])
45
+ module_name = ENV['MODULE'] || 'Schema'
46
+
47
+ abort "#{output} does not exist. Generate it with `rake jade:schema`." unless File.exist?(output)
19
48
 
20
- puts "wrote #{output}"
49
+ JadeSql::SchemaGenerator
50
+ .generate(File.read(input), tables:, columns:, module_name:)
51
+ .then { JadeSql::SchemaDrift.between(it, on_disk_modules(it, module_name, output)) }
52
+ .then { it.any? ? abort(it.to_s) : puts("#{output} matches #{input}.") }
53
+ end
21
54
  end
22
55
 
23
56
  # COLUMNS="patients:id,age;visits:id,seen_on" — tables separated by `;`,
@@ -1,3 +1,3 @@
1
1
  module JadeSql
2
- VERSION = '0.7.0'
2
+ VERSION = '0.9.0'
3
3
  end
data/lib/jade-sql.rb CHANGED
@@ -4,6 +4,7 @@ require_relative 'jade-sql/version'
4
4
 
5
5
  Jade.extension(__FILE__)
6
6
 
7
+ require_relative 'jade-sql/compiler'
7
8
  require_relative 'jade-sql/uuid_runtime'
8
9
 
9
10
  # Encoded `Sql.SqlError` values (the `[tag, ...args]` shape produced
@@ -11,29 +12,92 @@ require_relative 'jade-sql/uuid_runtime'
11
12
  # across the port boundary, and by anyone stubbing the ports in tests.
12
13
  module JadeSql
13
14
  module SqlErrors
14
- NOT_FOUND = ["NotFound"].freeze
15
- NOT_UNIQUE = ["NotUnique"].freeze
15
+ NOT_FOUND = ["NotFound"].freeze
16
+ TOO_MANY_ROWS = ["TooManyRows"].freeze
17
+ DEADLOCK = ["Deadlock"].freeze
18
+ SERIALIZATION_FAILURE = ["SerializationFailure"].freeze
19
+ STATEMENT_TIMEOUT = ["StatementTimeout"].freeze
20
+ LOCK_TIMEOUT = ["LockTimeout"].freeze
16
21
 
17
- def self.db_error(msg) = ["DbError", msg]
18
- def self.not_found = NOT_FOUND
19
- def self.not_unique = NOT_UNIQUE
20
- def self.conflict(name) = ["Conflict", name]
22
+ def self.db_error(msg)
23
+ ["DbError", msg]
24
+ end
25
+
26
+ def self.not_found
27
+ NOT_FOUND
28
+ end
29
+
30
+ def self.too_many_rows
31
+ TOO_MANY_ROWS
32
+ end
33
+
34
+ def self.unique_violation(name)
35
+ ["UniqueViolation", name]
36
+ end
37
+
38
+ def self.foreign_key_violation(name)
39
+ ["ForeignKeyViolation", name]
40
+ end
41
+
42
+ def self.check_violation(name)
43
+ ["CheckViolation", name]
44
+ end
45
+
46
+ def self.exclusion_violation(name)
47
+ ["ExclusionViolation", name]
48
+ end
49
+
50
+ def self.not_null_violation(column)
51
+ ["NotNullViolation", column]
52
+ end
53
+
54
+ def self.deadlock
55
+ DEADLOCK
56
+ end
57
+
58
+ def self.serialization_failure
59
+ SERIALIZATION_FAILURE
60
+ end
61
+
62
+ def self.statement_timeout
63
+ STATEMENT_TIMEOUT
64
+ end
65
+
66
+ def self.lock_timeout
67
+ LOCK_TIMEOUT
68
+ end
21
69
  end
22
70
  end
23
71
 
24
72
  module Sql
25
73
  module Errors
26
74
  class Error < StandardError; end
27
- class DbError < Error; end
28
- class NotFound < Error; end
29
- class NotUnique < Error; end
30
- class Conflict < Error; end
75
+ class DbError < Error; end
76
+ class NotFound < Error; end
77
+ class TooManyRows < Error; end
78
+ class UniqueViolation < Error; end
79
+ class ForeignKeyViolation < Error; end
80
+ class CheckViolation < Error; end
81
+ class ExclusionViolation < Error; end
82
+ class NotNullViolation < Error; end
83
+ class Deadlock < Error; end
84
+ class SerializationFailure < Error; end
85
+ class StatementTimeout < Error; end
86
+ class LockTimeout < Error; end
31
87
 
32
88
  BY_TAG = {
33
- "DbError" => DbError,
34
- "NotFound" => NotFound,
35
- "NotUnique" => NotUnique,
36
- "Conflict" => Conflict,
89
+ "DbError" => DbError,
90
+ "NotFound" => NotFound,
91
+ "TooManyRows" => TooManyRows,
92
+ "UniqueViolation" => UniqueViolation,
93
+ "ForeignKeyViolation" => ForeignKeyViolation,
94
+ "CheckViolation" => CheckViolation,
95
+ "ExclusionViolation" => ExclusionViolation,
96
+ "NotNullViolation" => NotNullViolation,
97
+ "Deadlock" => Deadlock,
98
+ "SerializationFailure" => SerializationFailure,
99
+ "StatementTimeout" => StatementTimeout,
100
+ "LockTimeout" => LockTimeout,
37
101
  }.freeze
38
102
  end
39
103
 
@@ -42,4 +106,18 @@ module Sql
42
106
  klass = Errors::BY_TAG.fetch(type, Errors::Error)
43
107
  raise klass, message
44
108
  end
109
+
110
+ # The boundary hands back `["ok", value]` or `["err", encoded]`, and the
111
+ # generated `fn!` raises `Jade::Interop::TaskError` for every failure alike.
112
+ # A controller cannot route that: `rescue_from Sql::Errors::NotFound` needs
113
+ # the variant. This raises the variant instead, so one `rescue_from` turns a
114
+ # missing row into a 404 the way `ActiveRecord::RecordNotFound` does.
115
+ #
116
+ # patient = Sql.unwrap!(App.find(id))
117
+ def self.unwrap!(result)
118
+ case result
119
+ in ["ok", value] then value
120
+ in ["err", encoded] then raise_typed!(encoded)
121
+ end
122
+ end
45
123
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jade-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - agustin
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-08-14 00:00:00.000000000 Z
10
+ date: 2026-09-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: jade-lang
@@ -15,17 +15,17 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.6.0
18
+ version: 0.12.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.6.0
26
- description: Query and mutation builders, schema generation from db/structure.sql,
27
- and an ActiveRecord-backed runtime for the Jade language. Renders typed queries
28
- to (String, List(Value)) and decodes rows into Jade structs.
25
+ version: 0.12.0
26
+ description: Query and write builders, schema generation from db/structure.sql, and
27
+ an ActiveRecord-backed runtime for the Jade language. Renders typed queries to (String,
28
+ List(Value)) and decodes rows into Jade structs.
29
29
  email:
30
30
  - agustincornu@fastmail.com
31
31
  executables:
@@ -40,12 +40,19 @@ files:
40
40
  - exe/jade-sql
41
41
  - lib/jade-sql.rb
42
42
  - lib/jade-sql/bin/generate_schema.rb
43
+ - lib/jade-sql/compiler.rb
44
+ - lib/jade-sql/compiler/assignable.rb
45
+ - lib/jade-sql/compiler/columns.rb
46
+ - lib/jade-sql/compiler/errors.rb
43
47
  - lib/jade-sql/runtime.rb
48
+ - lib/jade-sql/schema_drift.rb
44
49
  - lib/jade-sql/sql.jd
50
+ - lib/jade-sql/sql/expr.jd
51
+ - lib/jade-sql/sql/json.jd
45
52
  - lib/jade-sql/sql/loader.jd
46
- - lib/jade-sql/sql/mutation.jd
47
53
  - lib/jade-sql/sql/query.jd
48
54
  - lib/jade-sql/sql/uuid.jd
55
+ - lib/jade-sql/sql/write.jd
49
56
  - lib/jade-sql/tasks.rake
50
57
  - lib/jade-sql/uuid_runtime.rb
51
58
  - lib/jade-sql/version.rb