rdl 2.1.0 → 2.2.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.
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