Skip to content

Commit d9f6e02

Browse files
committed
Refactor - Make it easier to configure new functions and operators
Add the type information and the implementations of operators and functions closer together so they can keep in sync. For operators that accept multiple types and numbers of input arguments, I've chosen to share an implementation for the different input. This makes looking up operators slightly easier as you don't need to know input types to find the operator. Currently all operators and functions have a single return type that isn't dependent on their inputs. This isn't true for all functions (e.g COALESCE).
1 parent 479e0ed commit d9f6e02

4 files changed

Lines changed: 79 additions & 115 deletions

File tree

lib/rgsql.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'rgsql/nodes'
2+
require 'rgsql/callable'
23
require 'rgsql/tokenizer'
34
require 'rgsql/server'
45
require 'rgsql/parser'

lib/rgsql/callable.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
module RgSql
2+
Callable = Data.define(:name, :input_types, :output_type, :body)
3+
4+
class Callable
5+
include Nodes
6+
7+
OPERATORS = [
8+
new('+', [[Int, Int]], Int, ->(op1, op2) { Int.new(op1.value + op2.value) }),
9+
new('-', [[Int, Int], [Int]], Int, ->(op1, op2 = nil) { Int.new(op2 ? op1.value - op2.value : -op1.value) }),
10+
new('*', [[Int, Int]], Int, ->(op1, op2) { Int.new(op1.value * op2.value) }),
11+
new('/', [[Int, Int]], Int, ->(op1, op2) { Int.new(op1.value / op2.value) }),
12+
13+
new('NOT', [[Bool]], Bool, ->(op) { Bool.new(!op.value) }),
14+
new('AND', [[Bool, Bool]], Bool, ->(op1, op2) { Bool.new(op1.value && op2.value) }),
15+
new('OR', [[Bool, Bool]], Bool, ->(op1, op2) { Bool.new(op1.value || op2.value) }),
16+
17+
new('<', [[Bool, Bool], [Int, Int]], Bool, ->(op1, op2) { Bool.new(op1.to_i < op2.to_i) }),
18+
new('>', [[Bool, Bool], [Int, Int]], Bool, ->(op1, op2) { Bool.new(op1.to_i > op2.to_i) }),
19+
new('>=', [[Bool, Bool], [Int, Int]], Bool, ->(op1, op2) { Bool.new(op1.to_i >= op2.to_i) }),
20+
new('<=', [[Bool, Bool], [Int, Int]], Bool, ->(op1, op2) { Bool.new(op1.to_i <= op2.to_i) }),
21+
22+
new('=', [[Bool, Bool], [Int, Int]], Bool, ->(op1, op2) { Bool.new(op1.value == op2.value) }),
23+
new('<>', [[Bool, Bool], [Int, Int]], Bool, ->(op1, op2) { Bool.new(op1.value != op2.value) })
24+
].freeze
25+
26+
FUNCTIONS = [
27+
new('ABS', [[Int]], Int, ->(arg) { Int.new(arg.value.abs) }),
28+
new('MOD', [[Int, Int]], Int, ->(arg1, arg2) { Int.new(arg1.value % arg2.value) })
29+
].freeze
30+
31+
def self.find_operator(name)
32+
operator = OPERATORS.find { |callable| callable.name == name }
33+
operator || raise(ValidationError, "Cannot find operator #{name}")
34+
end
35+
36+
def self.find_function(name)
37+
function = FUNCTIONS.find { |callable| callable.name == name }
38+
function || raise(ValidationError, "Cannot find function #{name}")
39+
end
40+
41+
def call(inputs)
42+
body.call(*inputs)
43+
end
44+
45+
def accepts_types?(types)
46+
input_types.include?(types)
47+
end
48+
end
49+
end

lib/rgsql/expression.rb

Lines changed: 18 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ class << self
66
def type(expression, table)
77
case expression
88
when Operator
9-
type_operator(expression.operator, type_list(expression.operands, table))
9+
type_operator(expression, type_list(expression.operands, table))
1010
when Function
11-
type_function(expression.name, type_list(expression.arguments, table))
11+
type_function(expression, type_list(expression.arguments, table))
1212
when Int, Bool
1313
expression.class
1414
when Reference
@@ -19,9 +19,11 @@ def type(expression, table)
1919
def evaluate(expression, row = [], table = nil)
2020
case expression
2121
when Operator
22-
evaluate_operator(expression.operator, evaluate_list(expression.operands, row, table))
22+
operands = evaluate_list(expression.operands, row, table)
23+
Callable.find_operator(expression.operator).call(operands)
2324
when Function
24-
evaluate_function(expression.name, evaluate_list(expression.arguments, row, table))
25+
arguments = evaluate_list(expression.arguments, row, table)
26+
Callable.find_function(expression.name).call(arguments)
2527
when Reference
2628
table.get_reference(row, expression.name)
2729
when Int, Bool
@@ -33,54 +35,22 @@ def evaluate(expression, row = [], table = nil)
3335

3436
private
3537

36-
def type_operator(operator, operand_types)
37-
case operator
38-
when '+', '-', '*', '/'
39-
if operand_types.all? { |type| type == Int }
40-
Int
41-
else
42-
raise ValidationError, "Invalid types for operator #{operator}: #{operand_types}"
43-
end
44-
when 'NOT'
45-
if operand_types == [Bool]
46-
Bool
47-
else
48-
raise ValidationError, "Invalid types for operator #{operator}: #{operand_types}"
49-
end
50-
when 'AND', 'OR'
51-
if operand_types == [Bool, Bool]
52-
Bool
53-
else
54-
raise ValidationError, "Invalid types for operator #{operator}: #{operand_types}"
55-
end
56-
when '<', '>', '>=', '<=', '=', '<>'
57-
if [[Int, Int], [Bool, Bool]].include?(operand_types)
58-
Bool
59-
else
60-
raise ValidationError, "Invalid types for operator #{operator}: #{operand_types}"
61-
end
62-
else
63-
raise "unknown operator #{operator}"
38+
def type_operator(expression, operand_types)
39+
operator = Callable.find_operator(expression.operator)
40+
unless operator.accepts_types?(operand_types)
41+
raise(ValidationError, "Operator #{expression.operator} does not accept types #{operand_types}")
6442
end
43+
44+
operator.output_type
6545
end
6646

67-
def type_function(name, argument_types)
68-
case name
69-
when 'ABS'
70-
if argument_types == [Int]
71-
Int
72-
else
73-
raise ValidationError, "Invalid types for function #{name}: #{argument_types}"
74-
end
75-
when 'MOD'
76-
if argument_types == [Int, Int]
77-
Int
78-
else
79-
raise ValidationError, "Invalid types for function #{name}: #{argument_types}"
80-
end
81-
else
82-
raise ValidationError, "unknown function #{name}"
47+
def type_function(expression, argument_types)
48+
function = Callable.find_function(expression.name)
49+
unless function.accepts_types?(argument_types)
50+
raise(ValidationError, "Function #{expression.name} does not accept types #{argument_types}")
8351
end
52+
53+
function.output_type
8454
end
8555

8656
def evaluate_list(expressions, row, table)
@@ -90,71 +60,6 @@ def evaluate_list(expressions, row, table)
9060
def type_list(expressions, table)
9161
expressions.map { |expression| type(expression, table) }
9262
end
93-
94-
def evaluate_function(name, arguments)
95-
arg1 = arguments[0]
96-
arg2 = arguments[1]
97-
98-
case name
99-
when 'ABS'
100-
Int.new(arg1.value.abs)
101-
when 'MOD'
102-
Int.new(arg1.value % arg2.value)
103-
else
104-
raise "unknown function #{name}"
105-
end
106-
end
107-
108-
def evaluate_operator(operator, operands)
109-
op1 = operands[0]
110-
op2 = operands[1]
111-
112-
case operator
113-
when '+'
114-
Int.new(op1.value + op2.value)
115-
when '-'
116-
if operands.length == 1
117-
Int.new(-op1.value)
118-
else
119-
Int.new(op1.value - op2.value)
120-
end
121-
when '*'
122-
Int.new(op1.value * op2.value)
123-
when '/'
124-
Int.new(op1.value / op2.value)
125-
when 'NOT'
126-
Bool.new(!op1.value)
127-
when 'AND'
128-
Bool.new(op1.value && op2.value)
129-
when 'OR'
130-
Bool.new(op1.value || op2.value)
131-
when '<'
132-
Bool.new(to_integer(op1) < to_integer(op2))
133-
when '>'
134-
Bool.new(to_integer(op1) > to_integer(op2))
135-
when '>='
136-
Bool.new(to_integer(op1) >= to_integer(op2))
137-
when '<='
138-
Bool.new(to_integer(op1) <= to_integer(op2))
139-
when '='
140-
Bool.new(op1.value == op2.value)
141-
when '<>'
142-
Bool.new(op1.value != op2.value)
143-
else
144-
raise "unknown operator #{operator}"
145-
end
146-
end
147-
148-
def to_integer(argument)
149-
case argument
150-
when Int
151-
argument.value
152-
when Bool
153-
argument.value ? 1 : 0
154-
else
155-
raise "unexpected argument type #{argument.class}"
156-
end
157-
end
15863
end
15964
end
16065
end

lib/rgsql/nodes.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
module RgSql
22
module Nodes
3-
Int = Data.define(:value)
4-
Bool = Data.define(:value)
3+
Int = Data.define(:value) do
4+
def to_i
5+
value
6+
end
7+
end
8+
9+
Bool = Data.define(:value) do
10+
def to_i
11+
value ? 1 : 0
12+
end
13+
end
514

615
Select = Data.define(:select_list, :table)
716
SelectListItem = Data.define(:name, :expression)

0 commit comments

Comments
 (0)