rdl 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -6
  4. data/CHANGES.md +29 -0
  5. data/README.md +94 -26
  6. data/lib/rdl/boot.rb +82 -41
  7. data/lib/rdl/boot_rails.rb +5 -0
  8. data/lib/rdl/config.rb +9 -1
  9. data/lib/rdl/query.rb +2 -2
  10. data/lib/rdl/typecheck.rb +972 -225
  11. data/lib/rdl/types/annotated_arg.rb +8 -0
  12. data/lib/rdl/types/ast_node.rb +73 -0
  13. data/lib/rdl/types/bot.rb +8 -0
  14. data/lib/rdl/types/bound_arg.rb +63 -0
  15. data/lib/rdl/types/computed.rb +48 -0
  16. data/lib/rdl/types/dependent_arg.rb +9 -0
  17. data/lib/rdl/types/dynamic.rb +61 -0
  18. data/lib/rdl/types/finite_hash.rb +54 -9
  19. data/lib/rdl/types/generic.rb +33 -0
  20. data/lib/rdl/types/intersection.rb +8 -0
  21. data/lib/rdl/types/lexer.rex +6 -1
  22. data/lib/rdl/types/lexer.rex.rb +13 -1
  23. data/lib/rdl/types/method.rb +14 -0
  24. data/lib/rdl/types/nominal.rb +8 -0
  25. data/lib/rdl/types/non_null.rb +8 -0
  26. data/lib/rdl/types/optional.rb +8 -0
  27. data/lib/rdl/types/parser.racc +31 -5
  28. data/lib/rdl/types/parser.tab.rb +540 -302
  29. data/lib/rdl/types/rdl_types.rb +45 -0
  30. data/lib/rdl/types/singleton.rb +14 -1
  31. data/lib/rdl/types/string.rb +104 -0
  32. data/lib/rdl/types/structural.rb +8 -0
  33. data/lib/rdl/types/top.rb +8 -0
  34. data/lib/rdl/types/tuple.rb +32 -8
  35. data/lib/rdl/types/type.rb +54 -11
  36. data/lib/rdl/types/union.rb +41 -2
  37. data/lib/rdl/types/var.rb +10 -0
  38. data/lib/rdl/types/vararg.rb +8 -0
  39. data/lib/rdl/util.rb +13 -10
  40. data/lib/rdl/wrap.rb +271 -27
  41. data/lib/rdl_disable.rb +16 -2
  42. data/lib/types/active_record.rb +1 -0
  43. data/lib/types/core/array.rb +442 -23
  44. data/lib/types/core/basic_object.rb +3 -3
  45. data/lib/types/core/bigdecimal.rb +5 -0
  46. data/lib/types/core/class.rb +2 -0
  47. data/lib/types/core/dir.rb +3 -3
  48. data/lib/types/core/enumerable.rb +4 -4
  49. data/lib/types/core/enumerator.rb +1 -1
  50. data/lib/types/core/file.rb +4 -4
  51. data/lib/types/core/float.rb +203 -0
  52. data/lib/types/core/hash.rb +390 -15
  53. data/lib/types/core/integer.rb +223 -10
  54. data/lib/types/core/io.rb +2 -2
  55. data/lib/types/core/kernel.rb +8 -5
  56. data/lib/types/core/marshal.rb +3 -0
  57. data/lib/types/core/module.rb +3 -3
  58. data/lib/types/core/numeric.rb +0 -2
  59. data/lib/types/core/object.rb +5 -5
  60. data/lib/types/core/pathname.rb +2 -2
  61. data/lib/types/core/process.rb +1 -3
  62. data/lib/types/core/range.rb +1 -1
  63. data/lib/types/core/regexp.rb +2 -2
  64. data/lib/types/core/set.rb +1 -1
  65. data/lib/types/core/string.rb +408 -16
  66. data/lib/types/core/symbol.rb +3 -3
  67. data/lib/types/core/time.rb +1 -1
  68. data/lib/types/core/uri.rb +13 -13
  69. data/lib/types/rails/_helpers.rb +7 -1
  70. data/lib/types/rails/action_controller/mime_responds.rb +2 -0
  71. data/lib/types/rails/active_record/associations.rb +42 -30
  72. data/lib/types/rails/active_record/comp_types.rb +637 -0
  73. data/lib/types/rails/active_record/finder_methods.rb +1 -1
  74. data/lib/types/rails/active_record/model_schema.rb +28 -16
  75. data/lib/types/rails/active_record/relation.rb +5 -3
  76. data/lib/types/rails/active_record/sql-strings.rb +166 -0
  77. data/lib/types/rails/string.rb +1 -1
  78. data/lib/types/sequel.rb +1 -0
  79. data/lib/types/sequel/comp_types.rb +581 -0
  80. data/rdl.gemspec +5 -4
  81. data/test/test_alias.rb +4 -0
  82. data/test/test_array_types.rb +244 -0
  83. data/test/test_bound_types.rb +80 -0
  84. data/test/test_contract.rb +4 -0
  85. data/test/test_dsl.rb +5 -0
  86. data/test/test_dyn_comptype_checks.rb +206 -0
  87. data/test/test_generic.rb +21 -20
  88. data/test/test_hash_types.rb +322 -0
  89. data/test/test_intersection.rb +1 -0
  90. data/test/test_le.rb +29 -4
  91. data/test/test_member.rb +3 -1
  92. data/test/test_parser.rb +5 -0
  93. data/test/test_query.rb +1 -0
  94. data/test/test_rdl.rb +63 -28
  95. data/test/test_rdl_type.rb +4 -0
  96. data/test/test_string_types.rb +102 -0
  97. data/test/test_type_contract.rb +59 -37
  98. data/test/test_typecheck.rb +480 -75
  99. data/test/test_types.rb +17 -0
  100. data/test/test_wrap.rb +5 -0
  101. metadata +35 -5
  102. data/lib/types/rails/active_record/schema_types.rb +0 -51
@@ -1,2 +1,2 @@
1
1
  RDL.nowrap :'ActiveRecord::FinderMethods'
2
- RDL.type :'ActiveRecord::FinderMethods', :exists?, '(?String or Fixnum or Array<String> or Hash<String or Symbol, %any>) -> %bool'
2
+ #RDL.type :'ActiveRecord::FinderMethods', :exists?, '(?String or Integer or Array<String> or Hash<String or Symbol, %any>) -> %bool'
@@ -1,37 +1,49 @@
1
+ class ActiveRecord::Base
2
+ extend RDL::RDLAnnotate
3
+ end
4
+
1
5
  module ActiveRecord::ModelSchema::ClassMethods
2
6
  extend RDL::RDLAnnotate
3
7
 
4
8
  rdl_post(:load_schema!) { |ret| # load_schema! doesn't return anything interesting
9
+ =begin
5
10
  columns_hash.each { |name, col|
6
11
  t = RDL::Rails.column_to_rdl(col.type)
7
12
  if col.null
8
13
  # may be null; show nullability in return type
9
14
  rdl_type name, "() -> #{t} or nil" # getter
10
- rdl_type "#{name}=", "(#{t}) -> #{t} or nil" # setter
11
- rdl_type "write_attribute", "(:#{name}, #{t}) -> %bool"
12
- rdl_type "update_attribute", "(:#{name}, #{t}) -> %bool"
13
- rdl_type "update_column", "(:#{name}, #{t}) -> %bool"
15
+ rdl_type :"#{name}=", "(#{t}) -> #{t} or nil" # setter
16
+ rdl_type :write_attribute, "(:#{name}, #{t}) -> %bool"
17
+ rdl_type :update_attribute, "(:#{name}, #{t}) -> %bool"
18
+ rdl_type :update_column, "(:#{name}, #{t}) -> %bool"
14
19
  else
15
20
  # not null; can't truly check in type system but hint via the name
16
21
  rdl_type name, "() -> !#{t}" # getter
17
- rdl_type "#{name}=", "(!#{t}) -> !#{t}" # setter
18
- rdl_type "write_attribute", "(:#{name}, !#{t}) -> %bool"
19
- rdl_type "update_attribute", "(:#{name}, #{t}) -> %bool"
20
- rdl_type "update_column", "(:#{name}, #{t}) -> %bool"
22
+ rdl_type :"#{name}=", "(!#{t}) -> !#{t}" # setter
23
+ rdl_type :write_attribute, "(:#{name}, !#{t}) -> %bool"
24
+ rdl_type :update_attribute, "(:#{name}, #{t}) -> %bool"
25
+ rdl_type :update_column, "(:#{name}, #{t}) -> %bool"
21
26
  end
22
27
  }
23
28
 
24
29
  attribute_types = RDL::Rails.attribute_types(self)
25
- rdl_type 'self.find_by', '(' + attribute_types + ") -> #{self} or nil"
26
- rdl_type 'self.find_by!', '(' + attribute_types + ") -> #{self}"
27
- rdl_type 'update', '(' + attribute_types + ') -> %bool'
28
- rdl_type 'update_columns', '(' + attribute_types + ') -> %bool'
29
- rdl_type 'attributes=', '(' + attribute_types + ') -> %bool'
30
+ rdl_type :'self.find_by', '(' + attribute_types + ") -> #{self} or nil"
31
+ rdl_type :'self.find_by!', '(' + attribute_types + ") -> #{self}"
32
+ rdl_type :update, '(' + attribute_types + ') -> %bool'
33
+ rdl_type :update_columns, '(' + attribute_types + ') -> %bool'
34
+ rdl_type :'attributes=', '(' + attribute_types + ') -> %bool'
30
35
 
31
36
  # If called with String arguments, can't check types as precisely
32
- rdl_type 'write_attribute', '(String, %any) -> %bool'
33
- rdl_type 'update_attribute', '(String, %any) -> %bool'
34
- rdl_type 'update_column', '(String, %any) -> %bool'
37
+ rdl_type :write_attribute, '(String, %any) -> %bool'
38
+ rdl_type :update_attribute, '(String, %any) -> %bool'
39
+ rdl_type :update_column, '(String, %any) -> %bool'
40
+
41
+ rdl_type :'self.joins', "(Symbol or String) -> ActiveRecord::Associations::CollectionProxy<#{self.to_s}>"
42
+ rdl_type :'self.none', "() -> ActiveRecord::Associations::CollectionProxy<#{self.to_s}>"
43
+ rdl_type :'self.where', '(String, *%any) -> ActiveRecord::Associations::CollectionProxy<t>'
44
+ rdl_type :'self.where', '(**%any) -> ActiveRecord::Associations::CollectionProxy<t>'
45
+ =end
35
46
  true
36
47
  }
48
+
37
49
  end
@@ -2,10 +2,12 @@ RDL.nowrap :'ActiveRecord::Relation'
2
2
 
3
3
  RDL.type_params :'ActiveRecord::Relation', [:t], :all?
4
4
 
5
- RDL.type :'ActiveRecord::Relation', :[], '(Fixnum) -> t'
5
+ =begin
6
+ RDL.type :'ActiveRecord::Relation', :[], '(Integer) -> t'
6
7
  RDL.type :'ActiveRecord::Relation', :empty?, '() -> %bool'
7
8
  RDL.type :'ActiveRecord::Relation', :first, '() -> t'
8
- RDL.type :'ActiveRecord::Relation', :length, '() -> Fixnum'
9
- RDL.type :'ActiveRecord::Relation', :sort, '() {(t, t) -> Fixnum} -> Array<t>'
9
+ RDL.type :'ActiveRecord::Relation', :length, '() -> Integer'
10
+ RDL.type :'ActiveRecord::Relation', :sort, '() {(t, t) -> Integer} -> Array<t>'
10
11
  RDL.type :'ActiveRecord::Relation', :each, '() -> Enumerator<t>'
11
12
  RDL.type :'ActiveRecord::Relation', :each, '() { (t) -> %any } -> Array<t>'
13
+ =end
@@ -0,0 +1,166 @@
1
+ require 'sql-parser'
2
+
3
+ # Mocking the SQLVistor internal class behavior to do our own stuff
4
+ class ASTVisitor
5
+ def initialize(table, targs)
6
+ @table = table # default table name, if we don't have a qualified column name
7
+ @targs = targs
8
+ end
9
+
10
+ def visit(node)
11
+ node.accept(self)
12
+ end
13
+
14
+ def binary_op(o)
15
+ table, column = visit(o.left)
16
+ ident = visit(o.right)
17
+ query_type = @targs[ident] || @targs.last
18
+ schema_type = RDL::Globals.ar_db_schema[table.classify.to_sym].params[0].elts[column.to_sym]
19
+ query_type = query_type.elts[column.to_sym] if query_type.is_a? RDL::Type::FiniteHashType
20
+ # puts query_type, schema_type
21
+ raise RDL::Typecheck::StaticTypeError, "type error" unless query_type <= schema_type
22
+ end
23
+
24
+ alias_method :visit_Greater, :binary_op
25
+ alias_method :visit_Equals, :binary_op
26
+ alias_method :visit_Less, :binary_op
27
+ alias_method :visit_GreaterOrEquals, :binary_op
28
+
29
+ def visit_And(o)
30
+ visit(o.left)
31
+ visit(o.right)
32
+ end
33
+
34
+ def visit_Subquery(o)
35
+ raise RDL::Typecheck::StaticTypeError, "only works with SELECT queries now" unless o.query_specification.is_a? SQLParser::Statement::Select
36
+ select_query = o.query_specification
37
+ raise RDL::Typecheck::StaticTypeError, "expected only 1 column in SELECT sub queries" unless select_query.list.is_a? SQLParser::Statement::SelectList and select_query.list.columns.length == 1
38
+ column = select_query.list.columns[0].name
39
+ table = select_query.table_expression.from_clause.tables[0].name
40
+ visitor = ASTVisitor.new table, @targs
41
+ search_cond = select_query.table_expression.where_clause.search_condition
42
+ visitor.visit(search_cond)
43
+ RDL::Type::GenericType.new(RDL::Type::NominalType.new(Array), RDL::Globals.ar_db_schema[table.classify.to_sym].params[0].elts[column.to_sym])
44
+ end
45
+
46
+ def visit_In(o)
47
+ table, column = visit(o.left)
48
+ ident = visit(o.right)
49
+ # TODO: add a case where ident is an integer and targs doesn't have named params, but just ?-ed params
50
+ if ident.is_a? Integer and @targs.last.is_a? RDL::Type::FiniteHashType
51
+ # query_params is a finite hash
52
+ query_params = @targs.last.elts
53
+ # this is a hack, assumes keys are in order, which isn't necessarily true
54
+ query_type = query_params[query_params.keys[ident - 1]]
55
+ elsif ident.is_a? RDL::Type::GenericType
56
+ query_type = ident
57
+ else
58
+ # puts "(TODO) Unexpected"
59
+ end
60
+
61
+ schema_type = RDL::Globals.ar_db_schema[table.classify.to_sym].params[0].elts[column.to_sym]
62
+ # IN works with arrays
63
+ promoted = if query_type.is_a?(RDL::Type::TupleType) then query_type.promote else query_type end
64
+ if promoted.is_a? RDL::Type::GenericType
65
+ # base type is Array, maybe add check?
66
+ raise RDL::Typecheck::StaticTypeError, "type error" unless promoted.params[0] <= schema_type
67
+ else
68
+ raise RDL::Typecheck::StaticTypeError, "some other type after promotion"
69
+ end
70
+ end
71
+
72
+ def visit_Not(o)
73
+ visit(o.value)
74
+ end
75
+
76
+ def visit_Integer(o)
77
+ return o.value
78
+ end
79
+
80
+ def visit_QualifiedColumn(o)
81
+ [o.table.name, o.column.name]
82
+ end
83
+
84
+ def visit_Column(o)
85
+ [@table, o.name]
86
+ end
87
+
88
+ def visit_InValueList(o)
89
+ o.values.value
90
+ end
91
+ end
92
+
93
+ def handle_sql_strings(trec, targs)
94
+ parser = SQLParser::Parser.new
95
+
96
+ case trec
97
+ when RDL::Type::GenericType
98
+ if trec.base.klass == ActiveRecord_Relation
99
+ handle_sql_strings trec.params[0], targs
100
+ elsif trec.base.klass == JoinTable
101
+ # works only for the base class right now, need to extend for the params as well
102
+ base_klass = trec.params[0]
103
+ joined_with = trec.params[1]
104
+ case joined_with
105
+ when RDL::Type::UnionType
106
+ joined_with.types.each do |klass|
107
+ # add the joining association column on this
108
+ sql_query = "SELECT * FROM `#{base_klass.name.tableize}` INNER JOIN `#{klass.name.tableize}` ON a.id = b.a_id WHERE #{build_string_from_precise_string(targs)}"
109
+ # puts sql_query
110
+ begin
111
+ ast = parser.scan_str(sql_query)
112
+ rescue Racc::ParseError => e
113
+ # puts "There was a parse error with above query, moving on"
114
+ return
115
+ end
116
+ search_cond = ast.query_expression.table_expression.where_clause.search_condition
117
+ visitor = ASTVisitor.new base_klass.name.tableize, targs
118
+ visitor.visit(search_cond)
119
+ end
120
+ else
121
+ # TODO
122
+ # puts "== TODO =="
123
+ end
124
+ else
125
+ # puts "UNEXPECTED #{trec}, #{targs}"
126
+ end
127
+ when RDL::Type::NominalType
128
+ base_klass = trec
129
+ sql_query = "SELECT * FROM `#{base_klass.name.tableize}` WHERE #{build_string_from_precise_string(targs)}"
130
+ # puts sql_query
131
+ ast = parser.scan_str(sql_query)
132
+ search_cond = ast.query_expression.table_expression.where_clause.search_condition
133
+ visitor = ASTVisitor.new base_klass.name.tableize, targs
134
+ visitor.visit(search_cond)
135
+ when RDL::Type::SingletonType
136
+ base_klass = trec
137
+ sql_query = "SELECT * FROM `#{base_klass.val.to_s.tableize}` WHERE #{build_string_from_precise_string(targs)}"
138
+ # puts sql_query
139
+ ast = parser.scan_str(sql_query)
140
+ search_cond = ast.query_expression.table_expression.where_clause.search_condition
141
+ visitor = ASTVisitor.new base_klass.val.to_s.tableize, targs
142
+ visitor.visit(search_cond)
143
+ else
144
+ # puts "UNEXPECTED #{trec}, #{targs}"
145
+ end
146
+ end
147
+
148
+ def build_string_from_precise_string(args)
149
+ str = args[0]
150
+ raise "Bad type!" unless str.is_a? RDL::Type::PreciseStringType
151
+ # TODO: handles only non-interpolated strings for now
152
+ base_query = str.vals[0]
153
+
154
+ # Get rid of SQL functions here, that just ends up confusing the parser anyway
155
+ base_query.gsub!('LOWER(', '(')
156
+
157
+ counter = 1
158
+ if args[1].is_a? RDL::Type::FiniteHashType
159
+ # the query has named params
160
+ args[1].elts.keys.each { |k| base_query.gsub!(":#{k}", counter.to_s); counter += 1 }
161
+ else
162
+ # the query has ? symbols
163
+ args[1..-1].each { |t| base_query.sub!('?', counter.to_s); counter += 1}
164
+ end
165
+ base_query
166
+ end
@@ -1,3 +1,3 @@
1
- RDL.type :String, :truncate, '(Fixnum) -> String'
1
+ RDL.type :String, :truncate, '(Integer) -> String'
2
2
  RDL.type :String, :strftime, '(String) -> String'
3
3
  RDL.type :String, :sanitize, '() -> String'
@@ -0,0 +1 @@
1
+ Dir[File.dirname(__FILE__) + "/sequel/*.rb"].each { |f| require f }
@@ -0,0 +1,581 @@
1
+ class SequelDB
2
+ extend RDL::Annotate
3
+
4
+ type 'self.[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
5
+ type '[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
6
+ type :transaction, "() { () -> %any } -> self", wrap: false
7
+
8
+
9
+ type RDL::Globals, 'self.seq_db_schema', "()-> Hash<Symbol, RDL::Type::FiniteHashType>", wrap: false, effect: [:+, :+]
10
+
11
+ def self.gen_output_type(targ)
12
+ case targ
13
+ when RDL::Type::SingletonType
14
+ t = RDL::Globals.seq_db_schema[RDL.type_cast(targ.val, "Symbol", force: true)]
15
+ raise "no schema for table #{targ}" if t.nil?
16
+ new_t = t.elts.clone.merge({__selected: RDL::Globals.types[:nil], __last_joined: targ, __all_joined: targ, __orm: RDL::Globals.types[:false] })
17
+ new_fht = RDL::Type::FiniteHashType.new(new_t, nil)
18
+ return RDL::Type::GenericType.new(RDL::Type::NominalType.new(Table), new_fht)
19
+ else
20
+ raise "unexpected type"
21
+ end
22
+ end
23
+
24
+ RDL.type SequelDB, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
25
+ end
26
+
27
+ module Sequel::Mysql2; end
28
+
29
+ class Sequel::Mysql2::Database
30
+ extend RDL::Annotate
31
+ ## This class is identical to SequelDB (above), except its name.
32
+ ## Necessary to support different apps.
33
+
34
+ type 'self.[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
35
+ type '[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
36
+ type :transaction, "() { () -> %any } -> self", wrap: false
37
+
38
+
39
+ type RDL::Globals, 'self.seq_db_schema', "()-> Hash<Symbol, RDL::Type::FiniteHashType>", wrap: false, effect: [:+, :+]
40
+
41
+ def self.gen_output_type(targ)
42
+ case targ
43
+ when RDL::Type::SingletonType
44
+ t = RDL::Globals.seq_db_schema[RDL.type_cast(targ.val, "Symbol", force: true)]
45
+ raise "no schema for table #{targ}" if t.nil?
46
+ new_t = t.elts.clone.merge({__selected: RDL::Globals.types[:nil], __last_joined: targ, __all_joined: targ, __orm: RDL::Globals.types[:false] })
47
+ new_fht = RDL::Type::FiniteHashType.new(new_t, nil)
48
+ return RDL::Type::GenericType.new(RDL::Type::NominalType.new(Table), new_fht)
49
+ else
50
+ raise "unexpected type #{targ.class}"
51
+ end
52
+ end
53
+ RDL.type Sequel::Mysql2::Database, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
54
+
55
+ end
56
+
57
+
58
+ module Sequel
59
+ extend RDL::Annotate
60
+
61
+ type 'self.sqlite', '() -> DBVal', wrap: false
62
+
63
+ type 'self.[]', '(Symbol) -> ``gen_output_type(targs[0])``', wrap: false
64
+ type 'self.qualify', '(Symbol, Symbol) -> ``qualify_output_type(targs)``', wrap: false
65
+
66
+ def self.gen_output_type(targ)
67
+ case targ
68
+ when RDL::Type::SingletonType
69
+ RDL::Type::GenericType.new(RDL::Type::NominalType.new(SeqIdent), targ)
70
+ else
71
+ raise "unexpected type"
72
+ end
73
+ end
74
+ RDL.type Sequel, 'self.gen_output_type', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
75
+
76
+ def self.qualify_output_type(targs)
77
+ raise "unexpected types" unless targs.all? { |a| a.is_a?(RDL::Type::SingletonType) }
78
+ RDL::Type::GenericType.new(RDL::Type::NominalType.new(SeqQualIdent), targs[0], targs[1])
79
+ end
80
+ RDL.type Sequel, 'self.qualify_output_type', "(Array<RDL::Type::Type>) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
81
+ end
82
+ class SeqIdent
83
+ extend RDL::Annotate
84
+ type_params [:t], :all?
85
+ type :[], '(Symbol) -> ``gen_output_type(trec, targs[0])``', wrap: false
86
+
87
+ def self.gen_output_type(trec, targ)
88
+ case trec
89
+ when RDL::Type::GenericType
90
+ param = trec.params[0]
91
+ case targ
92
+ when RDL::Type::SingletonType
93
+ return RDL::Type::GenericType.new(RDL::Type::NominalType.new(SeqQualIdent), param, targ)
94
+ else
95
+ raise "expected singleton"
96
+ end
97
+ else
98
+ raise "unexpected trec type"
99
+ end
100
+ end
101
+ RDL.type SeqIdent, 'self.gen_output_type', "(RDL::Type::Type, RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
102
+
103
+ end
104
+
105
+ class SeqQualIdent
106
+ extend RDL::Annotate
107
+ type_params [:table, :column], :all?
108
+
109
+ end
110
+
111
+ class Table
112
+ extend RDL::Annotate
113
+ type_params [:t], :all?
114
+
115
+ type :join, "(Symbol, %any) -> ``join_ret_type(trec, targs)``", wrap: false
116
+ type :join, "(Symbol, %any, %any) -> ``join_ret_type(trec, targs)``", wrap: false
117
+
118
+ RDL.rdl_alias :Table, :inner_join, :join
119
+ RDL.rdl_alias :Table, :left_join, :join
120
+ RDL.rdl_alias :Table, :left_outer_join, :join
121
+
122
+ def self.get_schema(hash)
123
+ hash.select { |key, val| ![:__last_joined, :__all_joined, :__selected, :__orm].member?(key) }
124
+ end
125
+ RDL.type Table, 'self.get_schema', "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", typecheck: :type_code, wrap: false, effect: [:+, :+]
126
+
127
+ def self.get_all_joined(t)
128
+ case t
129
+ when RDL::Type::SingletonType
130
+ sing = RDL.type_cast(t, "RDL::Type::SingletonType<Symbol>", force: true)
131
+ raise "unexpected type #{t} in __all_joined clause" unless sing.val.is_a?(Symbol)
132
+ return [sing.val]
133
+ when RDL::Type::UnionType
134
+ all = t.types.map { |subt|
135
+ raise "unexpected type #{subt} in union type within __all_joined clause" unless subt.is_a?(RDL::Type::SingletonType) && RDL.type_cast(subt, "RDL::Type::SingletonType<Symbol>", force: true).val.is_a?(Symbol)
136
+ RDL.type_cast(subt, "RDL::Type::SingletonType<Symbol>", force: true).val
137
+ }
138
+ return all
139
+ when nil
140
+ return RDL.type_cast([], "Array<Symbol>", force: true)
141
+ else
142
+ raise "unexpected type #{t} in __all_joined clause"
143
+ end
144
+ end
145
+ RDL.type Table, 'self.get_all_joined', "(RDL::Type::Type) -> Array<Symbol>", typecheck: :type_code, wrap: false, effect: [:+, :+]
146
+
147
+ def self.join_ret_type(trec, targs)
148
+ raise RDL::Typecheck::StaticTypeError, "Unexpected number of arguments to `join`." unless targs.size == 2
149
+ targ1, targ2 = *targs
150
+ raise RDL::Typecheck::StaticTypeError, "Unexpected second argument type #{targ2} to `join`." unless targ2.is_a?(RDL::Type::FiniteHashType)
151
+ arg_join_column = RDL.type_cast(RDL.type_cast(targ2, "RDL::Type::FiniteHashType").elts.keys[0], "Symbol", force: true) ## column name of arg table which is joined on
152
+ rec_join_column = RDL.type_cast(RDL.type_cast(targ2, "RDL::Type::FiniteHashType").elts[arg_join_column], "Symbol", force: true) ## column name of receiver table which is joined on
153
+ case trec
154
+ when RDL::Type::GenericType
155
+ raise RDL::Typecheck::StaticTypeError, "unexpceted generic type in call to join" unless trec.base.name == "Table"
156
+ receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
157
+ receiver_schema = get_schema(receiver_param)
158
+ join_source_schema = get_schema(RDL::Globals.seq_db_schema[RDL.type_cast(receiver_param[:__last_joined], "RDL::Type::SingletonType<Symbol>", force: true).val].elts)
159
+ rec_all_joined = get_all_joined(receiver_param[:__all_joined])
160
+
161
+ case rec_join_column
162
+ when RDL::Type::SingletonType
163
+ ## given symbol for second column to join on
164
+ if rec_join_column.to_s.include?("__")
165
+ ## qualified column name in old versions of sequel.
166
+ check_qual_column(rec_join_column, rec_all_joined)
167
+ else
168
+ raise RDL::Typecheck::StaticTypeError, "No column #{rec_join_column} for receiver in call to `join`." if join_source_schema[rec_join_column.val].nil?
169
+ end
170
+ when RDL::Type::GenericType
171
+ ## given qualified column, e.g. Sequel[:people][:name]
172
+ raise RDL::Typecheck::StaticTypeError, "unexpected generic type #{rec_join_column}" unless rec_join_column.base.name == "SeqQualIdent"
173
+ qual_table, qual_column = rec_join_column.params.map { |t| t.val }
174
+ raise RDL::Typecheck::StaticTypeError, "qualified table #{qual_table} is not joined in receiver table, and so its columns cannot be joined on" unless rec_all_joined.include?(qual_table)
175
+ qual_table_schema = get_schema(RDL::Globals.seq_db_schema[qual_table].elts)
176
+ raise RDL::Typecheck::StaticTypeError, "No column #{qual_column} in table #{qual_table}." if qual_table_schema[qual_column].nil?
177
+ else
178
+ raise "Unexpected column #{rec_join_column} to join on"
179
+ end
180
+ case targ1
181
+ when RDL::Type::SingletonType
182
+ val = RDL.type_cast(targ1.val, "Symbol", force: true)
183
+ raise RDL::Typecheck::StaticTypeError, "Expected Symbol for first argument to `join`." unless val.is_a?(Symbol)
184
+ table_name = val
185
+ table_schema = RDL::Globals.seq_db_schema[table_name]
186
+ raise "No schema found for table #{table_name}." unless table_schema
187
+ arg_schema = get_schema(table_schema.elts) ## look up table schema for argument
188
+ raise RDL::Typecheck::StaticTypeError, "No column #{arg_join_column} for arg in call to `join`." if arg_schema[arg_join_column].nil?
189
+ result_schema = receiver_schema.merge(arg_schema).merge({ __all_joined: RDL::Type::UnionType.new(*[receiver_param[:__all_joined], targ1]), __last_joined: targ1, __selected: receiver_param[:__selected], __orm: receiver_param[:__orm] }) ## resulting schema as hash
190
+ result_fht = RDL::Type::FiniteHashType.new(result_schema, nil) ## resulting schema as FiniteHashType
191
+
192
+ return RDL::Type::GenericType.new(trec.base, result_fht)
193
+ when RDL::Type::NominalType
194
+ ## TODO: this will catch case that first argument is a non-singleton Symbol
195
+ raise "not implemented, likely not needed in practice"
196
+ else
197
+ raise RDL::Typecheck::StaticTypeError, "Unexpected type of first argument to `join`."
198
+ end
199
+ when RDL::Type::NominalType
200
+ raise RDL::Typecheck::StaticTypeError unless trec.name == "Table"
201
+ else
202
+ raise RDL::Typecheck::StaticTypeError, "Unexpected receiver type in call to `join`."
203
+ end
204
+ end
205
+ RDL.type Table, 'self.join_ret_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
206
+
207
+
208
+ type :insert, "(``insert_arg_type(trec, targs)``) -> Integer", wrap: false
209
+ type :insert, "(``insert_arg_type(trec, targs, true)``, %any) -> Integer", wrap: false
210
+ type :where, "(``where_arg_type(trec, targs)``) -> self", wrap: false
211
+ type :where, "(``where_arg_type(trec, targs, true)``, %any) -> self", wrap: false
212
+ type :exclude, "(``where_arg_type(trec, targs)``) -> self", wrap: false
213
+ type :exclude, "(``where_arg_type(trec, targs, true)``, %any) -> self", wrap: false
214
+ type :[], "(``where_arg_type(trec, targs)``) -> ``first_output(trec)``", wrap: false
215
+ type :first, "() -> ``first_output(trec)``", wrap: false
216
+ type :first, "(``if targs[0] then where_arg_type(trec, targs) else RDL::Globals.types[:bot] end``) -> ``first_output(trec)``", wrap: false
217
+ type :get, '(``get_input(trec)``) -> ``get_output(trec, targs)``', wrap: false
218
+ type :order, '(``order_input(trec, targs)``) -> self', wrap: false
219
+ type Sequel, 'self.desc', '(%any) -> ``targs[0]``', wrap: false ## args will ultimately be checked by `order`
220
+ type :select_map, '(Symbol) -> ``select_map_output(trec, targs, :select_map)``', wrap: false
221
+ type :pluck, '(Symbol) -> ``select_map_output(trec, targs, :select_map)``', wrap: false
222
+ type :any?, "() -> %bool", wrap: false
223
+ type :select, "(*%any) -> ``select_map_output(trec, targs, :select)``", wrap: false
224
+ type :all, "() -> ``all_output(trec)``", wrap: false
225
+ type Sequel, 'self.lit', "(%any) -> String", wrap: false
226
+ type :server, "(Symbol) -> self", wrap: false
227
+ type :empty?, '() -> %bool', wrap: false
228
+ type :update, "(``insert_arg_type(trec, targs)``) -> Integer", wrap: false
229
+ type :count, "() -> Integer", wrap: false
230
+ type :map, "() { (``map_block_input(trec)``) -> x } -> Array<x>", wrap: false
231
+ type :each, "() { (``map_block_input(trec)``) -> x } -> self", wrap: false
232
+ type :import, "(``import_arg_type(trec, targs)``, Array<u>) -> Array<String>", wrap: false
233
+
234
+ def self.order_input(trec, targs)
235
+ case trec
236
+ when RDL::Type::GenericType
237
+ trp0 = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true)
238
+ sym_keys = get_schema(trp0.elts).keys
239
+ all_joined = get_all_joined(trp0.elts[:__all_joined])
240
+ targs.each { |a|
241
+ case a
242
+ when RDL::Type::SingletonType
243
+ return RDL::Globals.types[:bot] unless sym_keys.include?(a.val)
244
+ when RDL::Type::GenericType
245
+ return RDL::Globals.types[:bot] unless a.base.name == "SeqQualIdent"
246
+ check_qual_column(a, all_joined)
247
+ end
248
+ }
249
+ return RDL::Type::VarargType.new(RDL::Type::UnionType.new(*targs))
250
+ else
251
+ raise "unexpected type #{trec}"
252
+ end
253
+ end
254
+ RDL.type Table, 'self.order_input', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
255
+
256
+ def self.map_block_input(trec)
257
+ schema = get_schema(RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts)
258
+ RDL::Type::FiniteHashType.new(schema, nil)
259
+ end
260
+ RDL.type Table, 'self.map_block_input', "(RDL::Type::GenericType) -> RDL::Type::FiniteHashType", typecheck: :type_code, wrap: false, effect: [:+, :+]
261
+
262
+ def self.all_output(trec)
263
+ f = first_output(trec)
264
+ if f.is_a?(RDL::Type::FiniteHashType)
265
+ trp0 = RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true)
266
+ selected = trp0.elts[:__selected]
267
+ all_joined = get_all_joined(trp0.elts[:__all_joined])
268
+ if !(selected == RDL::Globals.types[:nil])
269
+ ## something is selected
270
+ sel_arr = RDL.type_cast(selected.is_a?(RDL::Type::UnionType) ? RDL.type_cast(selected, "RDL::Type::UnionType").types : [selected], "Array<RDL::Type::SingletonType<Symbol>>", force: true)
271
+ new_hash = Hash[sel_arr.map { |sel|
272
+ if sel.val.to_s.include?("__")
273
+ t = check_qual_column(sel.val, all_joined)
274
+ _, col_name = sel.val.to_s.split "__"
275
+ col_name = col_name.to_sym
276
+ [col_name, t]
277
+ else
278
+ raise "no selected column found" unless (t = RDL.type_cast(f, "RDL::Type::FiniteHashType", force: true).elts[sel.val])
279
+ [sel.val, t]
280
+ end
281
+ }]
282
+ new_hash_type = RDL::Type::FiniteHashType.new(RDL.type_cast(new_hash, "Hash<%any, RDL::Type::Type>", force: true), nil)
283
+ return RDL::Type::GenericType.new(RDL::Globals.types[:array], new_hash_type)
284
+ else
285
+ return RDL::Type::GenericType.new(RDL::Globals.types[:array], f)
286
+ end
287
+ else
288
+ return RDL::Type::GenericType.new(RDL::Globals.types[:array], f)
289
+ end
290
+ end
291
+ RDL.type Table, 'self.all_output', "(RDL::Type::Type) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
292
+
293
+ def self.select_map_output(trec, targs, meth)
294
+ case trec
295
+ when RDL::Type::GenericType
296
+ raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table"
297
+ receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
298
+ all_joined = get_all_joined(receiver_param[:__all_joined])
299
+
300
+ map_types = targs.map { |arg|
301
+ case arg
302
+ when RDL::Type::SingletonType
303
+ column = RDL.type_cast(arg.val, "Symbol", force: true)
304
+ raise "unexpected arg type #{arg}" unless column.is_a?(Symbol)
305
+ raise "Ambiguous column identifier #{arg}." unless unique_ids?([column], receiver_param[:__all_joined])
306
+ if column.to_s.include?("__")
307
+ check_qual_column(column, all_joined)
308
+ else
309
+ raise "No column #{column} in receiver table." unless receiver_param[column]
310
+ receiver_param[column]
311
+ end
312
+ when RDL::Type::GenericType
313
+ raise "unexpected arg type #{arg}" unless arg.base.name == "SeqQualIdent"
314
+ check_qual_column(arg, all_joined)
315
+ else
316
+ raise "unexpected arg type #{arg}"
317
+ end
318
+ }
319
+
320
+ targs.each { |arg|
321
+ case arg
322
+ when RDL::Type::SingletonType
323
+ column = RDL.type_cast(arg.val, "Symbol", force: true)
324
+ raise "unexpected arg type #{arg}" unless column.is_a?(Symbol)
325
+ raise "Ambiguous column identifier #{arg}." unless unique_ids?([column], receiver_param[:__all_joined])
326
+ if column.to_s.include?("__")
327
+ map_types = map_types + [check_qual_column(column, all_joined)]
328
+ else
329
+ raise "No column #{column} in receiver table." unless receiver_param[column]
330
+ map_types = map_types + [receiver_param[column]]
331
+ end
332
+ when RDL::Type::GenericType
333
+ raise "unexpected arg type #{arg}" unless arg.base.name == "SeqQualIdent"
334
+ map_types = map_types + [check_qual_column(arg, all_joined)]
335
+ else
336
+ raise "unexpected arg type #{arg}"
337
+ end
338
+ }
339
+ if meth == :select
340
+ result_schema = receiver_param.clone.merge({ __selected: RDL::Type::UnionType.new(*targs).canonical })
341
+ return RDL::Type::GenericType.new(trec.base, RDL::Type::FiniteHashType.new(result_schema, nil))
342
+ elsif meth == :select_map
343
+ if targs.size >1
344
+ return RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::TupleType.new(*map_types))
345
+ else
346
+ return RDL::Type::GenericType.new(RDL::Globals.types[:array], map_types[0])
347
+ end
348
+ else
349
+ raise 'unexpected'
350
+ end
351
+ else
352
+ raise 'unexpected type #{trec}'
353
+ end
354
+ end
355
+ RDL.type Table, 'self.select_map_output', "(RDL::Type::Type, Array<RDL::Type::Type>, Symbol) -> RDL::Type::GenericType", typecheck: :type_code, wrap: false, effect: [:+, :+]
356
+
357
+ def self.get_input(trec)
358
+ case trec
359
+ when RDL::Type::GenericType
360
+ sym_keys = get_schema(RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts).keys
361
+ RDL::Type::UnionType.new(*RDL.type_cast(sym_keys.map { |k| RDL::Type::SingletonType.new(k) }, "Array<RDL::Type::Type>"))
362
+ else
363
+ raise 'unexpected type #{trec}'
364
+ end
365
+ end
366
+ RDL.type Table, 'self.get_input', "(RDL::Type::Type) -> RDL::Type::UnionType", typecheck: :type_code, wrap: false, effect: [:+, :+]
367
+
368
+ def self.get_output(trec, targs)
369
+ RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[RDL.type_cast(targs[0], "RDL::Type::SingletonType<Symbol>", force: true).val]
370
+ end
371
+ RDL.type Table, 'self.get_output', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
372
+
373
+ def self.first_output(trec)
374
+ case trec
375
+ when RDL::Type::GenericType
376
+ raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table"
377
+ receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
378
+ if !(receiver_param[:__orm] == RDL::Globals.types[:false])
379
+ receiver_param[:__orm]
380
+ else
381
+ RDL::Type::FiniteHashType.new(get_schema(receiver_param), nil)
382
+ end
383
+ else
384
+ raise 'unexpected type #{trec}'
385
+ end
386
+ end
387
+ RDL.type Table, 'self.first_output', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
388
+
389
+ def self.insert_arg_type(trec, targs, tuple=false)
390
+ raise "Cannot insert/update for joined table." if RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[:__all_joined].is_a?(RDL::Type::UnionType)
391
+ if tuple
392
+ schema_arg_tuple_type(trec, targs, :insert)
393
+ else
394
+ schema_arg_type(trec, targs, :insert)
395
+ end
396
+ end
397
+ RDL.type Table, 'self.insert_arg_type', "(RDL::Type::Type, Array<RDL::Type::Type>, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
398
+
399
+ def self.import_arg_type(trec, targs)
400
+ raise "Cannot import for joined table." if RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true).elts[:__all_joined].is_a?(RDL::Type::UnionType)
401
+ raise "Expected tuple for first arg to `import`, got #{targs[0]} instead." unless targs[0].is_a?(RDL::Type::TupleType)
402
+ arg1 = targs[1]
403
+ case arg1
404
+ when RDL::Type::TupleType
405
+ arg1.params.each { |t| schema_arg_tuple_type(trec, [targs[0], t], :import) } ## check each individual tuple inside second arg tuple
406
+ when RDL::Type::GenericType
407
+ raise "expected Array, got #{arg1}" unless (arg1.base == RDL::Globals.types[:array])
408
+ raise "`import` type not yet implemented for type #{arg1}" unless arg1.params[0].is_a?(RDL::Type::TupleType)
409
+ schema_arg_tuple_type(trec, [targs[0], arg1.params[0]], :import)
410
+ else
411
+ raise "Not yet implemented for type #{arg1}."
412
+ end
413
+ return targs[0]
414
+ end
415
+ RDL.type Table, 'self.import_arg_type', "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
416
+
417
+ def self.get_nominal_where_type(type)
418
+ ## `where` can accept arrays/tuples and tables with a single column selected
419
+ ## this method just extracts the parameter type
420
+ case type
421
+ when RDL::Type::GenericType
422
+ if type.base == RDL::Globals.types[:array]
423
+ type.params[0]
424
+ elsif type.base == RDL::Type::NominalType.new(Table)
425
+ schema = RDL.type_cast(type.params[0], "RDL::Type::FiniteHashType", force: true).elts
426
+ sel = schema[:__selected]
427
+ raise "Call to where expects table with a single column selected, got #{type}" unless sel.is_a?(RDL::Type::SingletonType)
428
+ nominal = schema[RDL.type_cast(sel, "RDL::Type::SingletonType", force: true).val]
429
+ raise "No type found for column #{sel} in call to `where`." unless nominal
430
+ nominal
431
+ else
432
+ type
433
+ end
434
+ when RDL::Type::TupleType
435
+ type = type.promote.params[0]
436
+ raise "`where` passed tuple containing different types." if type.is_a?(RDL::Type::UnionType)
437
+ type
438
+ else
439
+ type
440
+ end
441
+ end
442
+
443
+ RDL.type Table, 'self.get_nominal_where_type', "(RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
444
+
445
+ def self.schema_arg_type(trec, targs, meth)
446
+ return RDL::Type::NominalType.new(Hash) if targs.size != 1
447
+ case trec
448
+ when RDL::Type::GenericType
449
+ raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table"
450
+ receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
451
+ receiver_schema = get_schema(receiver_param)
452
+ all_joined = get_all_joined(receiver_param[:__all_joined])
453
+ arg0 = targs[0]
454
+ case arg0
455
+ when RDL::Type::FiniteHashType
456
+ insert_hash = arg0.elts
457
+ insert_hash.each { |column_name, type|
458
+ cn = RDL.type_cast(column_name, "Symbol", force: true)
459
+ if cn.to_s.include?("__") && (meth == :where)
460
+ check_qual_column(cn, all_joined, type)
461
+ else
462
+ raise RDL::Typecheck::StaticTypeError, "No column #{column_name} for receiver #{trec}." unless receiver_schema.has_key?(cn)
463
+ type = get_nominal_where_type(type) if (meth == :where)
464
+ raise RDL::Typecheck::StaticTypeError, "Incompatible column types #{type} and #{receiver_schema[column_name]} for column #{cn} in call to #{meth}." unless RDL::Type::Type.leq(type, receiver_schema[cn])
465
+ end
466
+ }
467
+ return arg0
468
+ else
469
+ return arg0 if (meth==:where) && targs[0] <= RDL::Globals.types[:string]
470
+ raise "TODO WITH #{trec} AND #{targs} AND #{meth}"
471
+ end
472
+ when RDL::Type::NominalType
473
+ ##TODO
474
+ else
475
+
476
+ end
477
+ end
478
+ RDL.type Table, 'self.schema_arg_type', "(RDL::Type::Type, Array<RDL::Type::Type>, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
479
+
480
+ ## [+ column_name +] if a symbol or SeqQualIdent Generic type of the qualified column name, e.g. :person__age
481
+ ## [+ all_joined +] is an array of symbols of joined tables (must check if qualifying table is a member)
482
+ ## [+ type +] is optional RDL type. If given, we check that it matches the type of the column in the schema.
483
+ ## returns type of given column
484
+ def self.check_qual_column(column_name, all_joined, type=nil)
485
+ case column_name
486
+ when RDL::Type::GenericType
487
+ cn = RDL.type_cast(column_name, "RDL::Type::GenericType", force: true)
488
+ raise "Expected qualified column type." unless cn.base.name == "SeqQualIdent"
489
+ qual_table, qual_column = cn.params.map { |t| RDL.type_cast(t, "RDL::Type::SingletonType<Symbol>", force: true).val }
490
+ else
491
+ ## symbol with name including underscores
492
+ qual_table, qual_column = RDL.type_cast(column_name, "Symbol", force: true).to_s.split "__"
493
+ qual_table = if qual_table.start_with?(":") then qual_table[1..-1].to_sym else qual_table.to_sym end
494
+ qual_column = qual_column.to_sym
495
+ end
496
+ raise RDL::Typecheck::StaticTypeError, "qualified table #{qual_table} is not joined in receiver table, cannot reference its columns" unless all_joined.include?(qual_table)
497
+ qual_table_schema = get_schema(RDL::Globals.seq_db_schema[qual_table].elts)
498
+ raise RDL::Typecheck::StaticTypeError, "No column #{qual_column} in table #{qual_table}." if qual_table_schema[qual_column].nil?
499
+ if type
500
+ types = (if type.is_a?(RDL::Type::UnionType) then RDL.type_cast(type, "RDL::Type::UnionType", force: true).types else [type] end)
501
+ types.each { |t|
502
+ t = RDL.type_cast(t, "RDL::Type::GenericType", force: true).params[0] if t.is_a?(RDL::Type::GenericType) && (RDL.type_cast(t, "RDL::Type::GenericType", force: true).base == RDL::Globals.types[:array]) ## only happens if meth is where, don't need to check
503
+ raise RDL::Typecheck::StaticTypeError, "Incompatible column types. Given #{t} but expected #{qual_table_schema[qual_column]} for column #{column_name}." unless RDL::Type::Type.leq(t, qual_table_schema[qual_column])
504
+ }
505
+ end
506
+ return qual_table_schema[qual_column]
507
+ end
508
+ RDL.type Table, 'self.check_qual_column', "(Symbol or RDL::Type::GenericType, Array<Symbol>, ?RDL::Type::Type) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
509
+
510
+ def self.schema_arg_tuple_type(trec, targs, meth)
511
+ return RDL::Type::NominalType.new(Array) if targs.size != 2
512
+ case trec
513
+ when RDL::Type::GenericType
514
+ raise RDL::Typecheck::StaticTypeError, 'unexpected type' unless trec.base.name == "Table"
515
+ receiver_param = RDL.type_cast(trec.params[0], "RDL::Type::FiniteHashType", force: true).elts
516
+ all_joined = get_all_joined(receiver_param[:__all_joined])
517
+ receiver_schema = get_schema(receiver_param)
518
+ if targs[0].is_a?(RDL::Type::TupleType) && targs[1].is_a?(RDL::Type::TupleType)
519
+ RDL.type_cast(targs[0], "RDL::Type::TupleType", force: true).params.each_with_index { |column_name, i|
520
+ cn = RDL.type_cast(column_name, "RDL::Type::SingletonType<Symbol>", force: true)
521
+ raise "Expected singleton symbol in call to insert, got #{column_name}" unless cn.is_a?(RDL::Type::SingletonType) && cn.val.is_a?(Symbol)
522
+ type = RDL.type_cast(targs[1], "RDL::Type::TupleType", force: true).params[i]
523
+ if cn.val.to_s.include?("__") && (meth == :where)
524
+ check_qual_column(cn.val, all_joined, type)
525
+ else
526
+ raise RDL::Typecheck::StaticTypeError, "No column #{column_name} for receiver in call to `insert`." unless receiver_schema.has_key?(cn.val)
527
+ type = get_nominal_where_type(type) if (meth == :where)
528
+ raise RDL::Typecheck::StaticTypeError, "Incompatible column types." unless RDL::Type::Type.leq(type, receiver_schema[cn.val])
529
+ end
530
+ }
531
+ return targs[0]
532
+ else
533
+ raise "not yet implemented for types #{targs[0]} and #{targs[1]}"
534
+ end
535
+ else
536
+ raise 'not yet implemented'
537
+ end
538
+ end
539
+ RDL.type Table, 'self.schema_arg_tuple_type', "(RDL::Type::Type, Array<RDL::Type::Type>, Symbol) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
540
+
541
+ def self.where_arg_type(trec, targs, tuple=false)
542
+ trp0 = RDL.type_cast(RDL.type_cast(trec, "RDL::Type::GenericType").params[0], "RDL::Type::FiniteHashType", force: true)
543
+ if trp0.elts[:__all_joined].is_a?(RDL::Type::UnionType)
544
+ arg0 = targs[0]
545
+ case arg0
546
+ when RDL::Type::TupleType
547
+ raise "Unexpected column type." unless arg0.params.all? { |t| t.is_a?(RDL::Type::SingletonType) && RDL.type_cast(t, "RDL::Type::SingletonType<Object>", force: true).val.is_a?(Symbol) }
548
+ raise "Ambigious identifier in call to where." unless unique_ids?(arg0.params.map { |t| RDL.type_cast(t, "RDL::Type::SingletonType<Symbol>", force: true).val }, trp0.elts[:__all_joined])
549
+ when RDL::Type::FiniteHashType
550
+ raise "Ambigious identifier in call to where." unless unique_ids?(RDL.type_cast(arg0.elts.keys, "Array<Symbol>", force: true), trp0.elts[:__all_joined])
551
+ else
552
+ raise "unexpected arg type #{arg0}"
553
+ end
554
+ end
555
+ if tuple
556
+ schema_arg_tuple_type(trec, targs, :where)
557
+ else
558
+ schema_arg_type(trec, targs, :where)
559
+ end
560
+ end
561
+ RDL.type Table, 'self.where_arg_type', "(RDL::Type::Type, Array<RDL::Type::Type>, ?%bool) -> RDL::Type::Type", typecheck: :type_code, wrap: false, effect: [:+, :+]
562
+
563
+ def self.unique_ids?(ids, joined)
564
+ j = get_all_joined(joined)
565
+ count = Hash[ids.map { |id| [id, 0] }]
566
+
567
+ j.each { |t1|
568
+ schema1 = RDL::Globals.seq_db_schema[t1]
569
+ raise "schema not found" unless schema1
570
+ j.each { |t2|
571
+ schema2 = RDL::Globals.seq_db_schema[t2]
572
+ ids.each { |id|
573
+ return false if schema1.elts.has_key?(id) && schema2.elts.has_key?(id) && (t1 != t2)
574
+ }
575
+ }
576
+ }
577
+ return true
578
+ end
579
+ RDL.type Table, 'self.unique_ids?', "(Array<Symbol>, RDL::Type::Type) -> %bool", typecheck: :type_code, wrap: false, effect: [:+, :+]
580
+
581
+ end