relationizer 0.2.3 → 0.3.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.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +20 -0
- data/lib/relationizer.rb +3 -4
- data/lib/relationizer/big_query.rb +138 -0
- data/lib/relationizer/postgresql.rb +2 -2
- data/lib/relationizer/version.rb +1 -1
- data/relationizer.gemspec +1 -1
- metadata +9 -12
- data/lib/rel_lib/relationizer.rb +0 -8
- data/lib/rel_lib/relationizer/version.rb +0 -3
- data/lib/relationizer/big_query/legacy.rb +0 -8
- data/lib/relationizer/big_query/standard.rb +0 -140
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c55b691cb2f721fd3fd39909e1745a72cb9743211587c6fdf477ba5e04fd209b
|
|
4
|
+
data.tar.gz: 7233397ac4d8ab8c4f29c2b1de073fa01634593884b53501f6bdfc1957e25f82
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '093102036edcd8201974a31edb553e35c0cb2e5895ec134e091c50d9bdfdf7377fdaedc6e934d08575eeecafd81602c0d976f7d1716b9a3ad35ac5f9984b11ee'
|
|
7
|
+
data.tar.gz: bdb16173d5c526e7fd86ae4129522479e9913cc0a33d63d3aa7fec753d09f604681767f08ee3cfe26594046225693ee00c3ff557d5f60ce9c102997807a5a13d
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Ruby
|
|
2
|
+
|
|
3
|
+
on: [push]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v1
|
|
12
|
+
- name: Set up Ruby 2.6
|
|
13
|
+
uses: actions/setup-ruby@v1
|
|
14
|
+
with:
|
|
15
|
+
ruby-version: 2.6.x
|
|
16
|
+
- name: Build and test with Rake
|
|
17
|
+
run: |
|
|
18
|
+
gem install bundler
|
|
19
|
+
bundle install --jobs 4 --retry 3
|
|
20
|
+
bundle exec rake
|
data/lib/relationizer.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
require_relative "relationizer/version"
|
|
2
2
|
require_relative "relationizer/postgresql"
|
|
3
|
-
require_relative "relationizer/big_query
|
|
3
|
+
require_relative "relationizer/big_query"
|
|
4
4
|
|
|
5
5
|
module Relationizer
|
|
6
|
-
|
|
7
|
-
end
|
|
6
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require 'bigdecimal'
|
|
2
|
+
require 'date'
|
|
3
|
+
|
|
4
|
+
module Relationizer
|
|
5
|
+
module BigQuery
|
|
6
|
+
class ReasonlessTypeError < StandardError; end
|
|
7
|
+
|
|
8
|
+
KNOWN_TYPES = [:INT64, :FLOAT64, :STRING, :BOOL, :TIMESTAMP, :DATE]
|
|
9
|
+
|
|
10
|
+
DEFAULT_TYPES = -> (obj) {
|
|
11
|
+
case obj
|
|
12
|
+
when Integer then :INT64
|
|
13
|
+
when BigDecimal then :FLOAT64
|
|
14
|
+
when Float then :FLOAT64
|
|
15
|
+
when String then :STRING
|
|
16
|
+
when TrueClass then :BOOL
|
|
17
|
+
when FalseClass then :BOOL
|
|
18
|
+
when Time then :TIMESTAMP
|
|
19
|
+
when DateTime then :TIMESTAMP
|
|
20
|
+
when Date then :DATE
|
|
21
|
+
when Array then :ARRAY
|
|
22
|
+
else
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def create_relation_literal(schema, tuples)
|
|
28
|
+
types = fixed_types(schema.values, tuples)
|
|
29
|
+
|
|
30
|
+
_types_exp = types_exp(schema.keys, types)
|
|
31
|
+
|
|
32
|
+
tuples_exp = tuples.map { |tuple|
|
|
33
|
+
tuple.zip(types).
|
|
34
|
+
map { |(col, type)| to_literal(col, type) }.
|
|
35
|
+
join(", ").
|
|
36
|
+
tap { |t| break "(#{t}#{', NULL' if tuple.length == 1})" }
|
|
37
|
+
}.join(", ").tap { |t| break "[#{t}]"}
|
|
38
|
+
|
|
39
|
+
select_exp = if schema.one?
|
|
40
|
+
"#{schema.keys.first}"
|
|
41
|
+
else
|
|
42
|
+
'*'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
"SELECT #{select_exp} FROM UNNEST(#{_types_exp}#{tuples_exp})"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def array_type(array)
|
|
51
|
+
classes = array.compact.map(&:class).uniq
|
|
52
|
+
|
|
53
|
+
unless classes.length == 1
|
|
54
|
+
raise ReasonlessTypeError.new("Ambiguous type of element in array: #{classes}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
DEFAULT_TYPES[array.first] || :STRING
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def types_exp(names, types)
|
|
61
|
+
case names.length
|
|
62
|
+
when 1
|
|
63
|
+
%Q{ARRAY<STRUCT<`#{names.first}` #{types.first}, `___dummy` STRING>>}
|
|
64
|
+
else
|
|
65
|
+
%Q{ARRAY<STRUCT<#{names.zip(types).map { |(name, type)| "`#{name}` #{type}" }.join(", ")}>>}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def many_candidate_check(types)
|
|
70
|
+
raise ReasonlessTypeError.new("Many candidate: #{types.join(', ')}") unless types.one?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def empty_candidate_check(types)
|
|
74
|
+
raise ReasonlessTypeError.new("Candidate nothing") if types.empty?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def fixed_types(schema, tuples)
|
|
78
|
+
tuples.transpose.zip(schema.to_a).map { |values, type|
|
|
79
|
+
next type if type
|
|
80
|
+
|
|
81
|
+
if values.map { |o| o.is_a?(Array) }.all?
|
|
82
|
+
types = values.
|
|
83
|
+
map(&method(:array_type)).uniq.
|
|
84
|
+
tap(&method(:empty_candidate_check)).
|
|
85
|
+
tap(&method(:many_candidate_check))
|
|
86
|
+
|
|
87
|
+
next "ARRAY<#{types.first}>".to_sym
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
values.
|
|
91
|
+
map(&DEFAULT_TYPES).compact.uniq.
|
|
92
|
+
tap(&method(:empty_candidate_check)).
|
|
93
|
+
tap(&method(:many_candidate_check)).
|
|
94
|
+
first || :STRING
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_literal(obj, type)
|
|
99
|
+
return "NULL" if obj.nil?
|
|
100
|
+
|
|
101
|
+
case type
|
|
102
|
+
when :ARRAY
|
|
103
|
+
t = array_type(obj)
|
|
104
|
+
obj.map { |e| to_literal(e, t) }.join(', ').tap { |s| break "[#{s}]"}
|
|
105
|
+
when /^ARRAY\<.+\>$/
|
|
106
|
+
t = /^ARRAY\<(.+)\>$/.match(type).to_a&.dig(1).to_sym
|
|
107
|
+
raise "Unknown type: #{t}" unless KNOWN_TYPES.include?(t)
|
|
108
|
+
obj.map { |e| to_literal(e, t) }.join(', ').tap { |s| break "[#{s}]"}
|
|
109
|
+
when :TIMESTAMP
|
|
110
|
+
%Q{'#{obj.strftime('%Y-%m-%d %H:%M:%S')}'}
|
|
111
|
+
when :STRING, :DATE
|
|
112
|
+
obj.to_s.gsub(/'/, "\'").tap do |s|
|
|
113
|
+
break "'#{s}'"
|
|
114
|
+
end
|
|
115
|
+
when :BOOL
|
|
116
|
+
case obj
|
|
117
|
+
when TrueClass, FalseClass
|
|
118
|
+
obj
|
|
119
|
+
else
|
|
120
|
+
!!obj
|
|
121
|
+
end
|
|
122
|
+
when :FLOAT64
|
|
123
|
+
case obj
|
|
124
|
+
when Float::INFINITY
|
|
125
|
+
"CAST('inf' AS FLOAT64)"
|
|
126
|
+
when -Float::INFINITY
|
|
127
|
+
"CAST('-inf' AS FLOAT64)"
|
|
128
|
+
else
|
|
129
|
+
obj
|
|
130
|
+
end
|
|
131
|
+
when :INT64
|
|
132
|
+
obj.to_s
|
|
133
|
+
else
|
|
134
|
+
raise "Unknown type: #{type}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -49,7 +49,7 @@ module Relationizer
|
|
|
49
49
|
|
|
50
50
|
def select_exp(schema, tuples)
|
|
51
51
|
tuples.transpose.zip(schema.to_a).map { |(values, (name, type))|
|
|
52
|
-
next "#{name}::#{type.to_s.upcase}
|
|
52
|
+
next %Q{"#{name}"::#{type.to_s.upcase}} if type
|
|
53
53
|
|
|
54
54
|
values.
|
|
55
55
|
map(&DEFAULT_TYPES).compact.uniq.
|
|
@@ -57,7 +57,7 @@ module Relationizer
|
|
|
57
57
|
tap(&method(:many_candidate_check)).
|
|
58
58
|
first.
|
|
59
59
|
to_s.upcase.
|
|
60
|
-
tap { |fixed_type| break "#{name}::#{fixed_type}
|
|
60
|
+
tap { |fixed_type| break %Q{"#{name}"::#{fixed_type}} }
|
|
61
61
|
}.join(", ")
|
|
62
62
|
end
|
|
63
63
|
|
data/lib/relationizer/version.rb
CHANGED
data/relationizer.gemspec
CHANGED
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
19
|
spec.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
-
spec.add_development_dependency "bundler"
|
|
21
|
+
spec.add_development_dependency "bundler"
|
|
22
22
|
spec.add_development_dependency "rake", "~> 10.0"
|
|
23
23
|
spec.add_development_dependency "test-unit", "~> 3.0.0"
|
|
24
24
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: relationizer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- yancya
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2019-12-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '0'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rake
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -59,6 +59,7 @@ executables: []
|
|
|
59
59
|
extensions: []
|
|
60
60
|
extra_rdoc_files: []
|
|
61
61
|
files:
|
|
62
|
+
- ".github/workflows/ruby.yml"
|
|
62
63
|
- ".gitignore"
|
|
63
64
|
- Gemfile
|
|
64
65
|
- LICENSE.txt
|
|
@@ -66,11 +67,8 @@ files:
|
|
|
66
67
|
- Rakefile
|
|
67
68
|
- bin/console
|
|
68
69
|
- bin/setup
|
|
69
|
-
- lib/rel_lib/relationizer.rb
|
|
70
|
-
- lib/rel_lib/relationizer/version.rb
|
|
71
70
|
- lib/relationizer.rb
|
|
72
|
-
- lib/relationizer/big_query
|
|
73
|
-
- lib/relationizer/big_query/standard.rb
|
|
71
|
+
- lib/relationizer/big_query.rb
|
|
74
72
|
- lib/relationizer/postgresql.rb
|
|
75
73
|
- lib/relationizer/version.rb
|
|
76
74
|
- relationizer.gemspec
|
|
@@ -93,8 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
93
91
|
- !ruby/object:Gem::Version
|
|
94
92
|
version: '0'
|
|
95
93
|
requirements: []
|
|
96
|
-
|
|
97
|
-
rubygems_version: 2.6.13
|
|
94
|
+
rubygems_version: 3.0.3
|
|
98
95
|
signing_key:
|
|
99
96
|
specification_version: 4
|
|
100
97
|
summary: Array<Array> to Relation ppoi String
|
data/lib/rel_lib/relationizer.rb
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
require 'bigdecimal'
|
|
2
|
-
require 'date'
|
|
3
|
-
|
|
4
|
-
module Relationizer
|
|
5
|
-
module BigQuery
|
|
6
|
-
module Standard
|
|
7
|
-
class ReasonlessTypeError < StandardError; end
|
|
8
|
-
|
|
9
|
-
KNOWN_TYPES = [:INT64, :FLOAT64, :STRING, :BOOL, :TIMESTAMP, :DATE]
|
|
10
|
-
|
|
11
|
-
DEFAULT_TYPES = -> (obj) {
|
|
12
|
-
case obj
|
|
13
|
-
when Integer then :INT64
|
|
14
|
-
when BigDecimal then :FLOAT64
|
|
15
|
-
when Float then :FLOAT64
|
|
16
|
-
when String then :STRING
|
|
17
|
-
when TrueClass then :BOOL
|
|
18
|
-
when FalseClass then :BOOL
|
|
19
|
-
when Time then :TIMESTAMP
|
|
20
|
-
when DateTime then :TIMESTAMP
|
|
21
|
-
when Date then :DATE
|
|
22
|
-
when Array then :ARRAY
|
|
23
|
-
else
|
|
24
|
-
nil
|
|
25
|
-
end
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
def create_relation_literal(schema, tuples)
|
|
29
|
-
types = fixed_types(schema.values, tuples)
|
|
30
|
-
|
|
31
|
-
_types_exp = types_exp(schema.keys, types)
|
|
32
|
-
|
|
33
|
-
tuples_exp = tuples.map { |tuple|
|
|
34
|
-
tuple.zip(types).
|
|
35
|
-
map { |(col, type)| to_literal(col, type) }.
|
|
36
|
-
join(", ").
|
|
37
|
-
tap { |t| break "(#{t}#{', NULL' if tuple.length == 1})" }
|
|
38
|
-
}.join(", ").tap { |t| break "[#{t}]"}
|
|
39
|
-
|
|
40
|
-
select_exp = if schema.one?
|
|
41
|
-
"#{schema.keys.first}"
|
|
42
|
-
else
|
|
43
|
-
'*'
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
"SELECT #{select_exp} FROM UNNEST(#{_types_exp}#{tuples_exp})"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
def array_type(array)
|
|
52
|
-
classes = array.compact.map(&:class).uniq
|
|
53
|
-
|
|
54
|
-
unless classes.length == 1
|
|
55
|
-
raise ReasonlessTypeError.new("Ambiguous type of element in array: #{classes}")
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
DEFAULT_TYPES[array.first] || :STRING
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def types_exp(names, types)
|
|
62
|
-
case names.length
|
|
63
|
-
when 1
|
|
64
|
-
%Q{ARRAY<STRUCT<#{names.first} #{types.first}, ___dummy STRING>>}
|
|
65
|
-
else
|
|
66
|
-
%Q{ARRAY<STRUCT<#{names.zip(types).map { |(name, type)| "#{name} #{type}" }.join(", ")}>>}
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def many_candidate_check(types)
|
|
71
|
-
raise ReasonlessTypeError.new("Many candidate: #{types.join(', ')}") unless types.one?
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def empty_candidate_check(types)
|
|
75
|
-
raise ReasonlessTypeError.new("Candidate nothing") if types.empty?
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def fixed_types(schema, tuples)
|
|
79
|
-
tuples.transpose.zip(schema.to_a).map { |values, type|
|
|
80
|
-
next type if type
|
|
81
|
-
|
|
82
|
-
if values.map { |o| o.is_a?(Array) }.all?
|
|
83
|
-
types = values.
|
|
84
|
-
map(&method(:array_type)).uniq.
|
|
85
|
-
tap(&method(:empty_candidate_check)).
|
|
86
|
-
tap(&method(:many_candidate_check))
|
|
87
|
-
|
|
88
|
-
next "ARRAY<#{types.first}>".to_sym
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
values.
|
|
92
|
-
map(&DEFAULT_TYPES).compact.uniq.
|
|
93
|
-
tap(&method(:empty_candidate_check)).
|
|
94
|
-
tap(&method(:many_candidate_check)).
|
|
95
|
-
first || :STRING
|
|
96
|
-
}
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def to_literal(obj, type)
|
|
100
|
-
return "NULL" if obj.nil?
|
|
101
|
-
|
|
102
|
-
case type
|
|
103
|
-
when :ARRAY
|
|
104
|
-
t = array_type(obj)
|
|
105
|
-
obj.map { |e| to_literal(e, t) }.join(', ').tap { |s| break "[#{s}]"}
|
|
106
|
-
when /^ARRAY\<.+\>$/
|
|
107
|
-
t = /^ARRAY\<(.+)\>$/.match(type).to_a&.dig(1).to_sym
|
|
108
|
-
raise "Unknown type: #{t}" unless KNOWN_TYPES.include?(t)
|
|
109
|
-
obj.map { |e| to_literal(e, t) }.join(', ').tap { |s| break "[#{s}]"}
|
|
110
|
-
when :TIMESTAMP
|
|
111
|
-
%Q{'#{obj.strftime('%Y-%m-%d %H:%M:%S')}'}
|
|
112
|
-
when :STRING, :DATE
|
|
113
|
-
obj.to_s.gsub(/'/, "\'").tap do |s|
|
|
114
|
-
break "'#{s}'"
|
|
115
|
-
end
|
|
116
|
-
when :BOOL
|
|
117
|
-
case obj
|
|
118
|
-
when TrueClass, FalseClass
|
|
119
|
-
obj
|
|
120
|
-
else
|
|
121
|
-
!!obj
|
|
122
|
-
end
|
|
123
|
-
when :FLOAT64
|
|
124
|
-
case obj
|
|
125
|
-
when Float::INFINITY
|
|
126
|
-
"CAST('inf' AS FLOAT64)"
|
|
127
|
-
when -Float::INFINITY
|
|
128
|
-
"CAST('-inf' AS FLOAT64)"
|
|
129
|
-
else
|
|
130
|
-
obj
|
|
131
|
-
end
|
|
132
|
-
when :INT64
|
|
133
|
-
obj.to_s
|
|
134
|
-
else
|
|
135
|
-
raise "Unknown type: #{type}"
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|