dumbo 0.0.1 → 0.0.3
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 +4 -4
- data/.gitignore +1 -0
- data/Gemfile +1 -1
- data/bin/dumbo +65 -24
- data/config/boot.rb +2 -3
- data/dumbo.gemspec +1 -2
- data/lib/dumbo.rb +17 -16
- data/lib/dumbo/aggregate.rb +3 -5
- data/lib/dumbo/base_type.rb +17 -17
- data/lib/dumbo/binding_loader.rb +50 -0
- data/lib/dumbo/cast.rb +5 -5
- data/lib/dumbo/composite_type.rb +4 -4
- data/lib/dumbo/db_task.rb +6 -5
- data/lib/dumbo/dependency_resolver.rb +17 -18
- data/lib/dumbo/enum_type.rb +3 -4
- data/lib/dumbo/extension.rb +64 -20
- data/lib/dumbo/extension_migrator.rb +11 -11
- data/lib/dumbo/extension_version.rb +29 -11
- data/lib/dumbo/function.rb +21 -21
- data/lib/dumbo/operator.rb +4 -6
- data/lib/dumbo/pg_object.rb +17 -21
- data/lib/dumbo/rake_task.rb +63 -53
- data/lib/dumbo/range_type.rb +6 -9
- data/lib/dumbo/test.rb +8 -0
- data/lib/dumbo/test/fixture.rb +51 -0
- data/lib/dumbo/test/helper.rb +64 -0
- data/lib/dumbo/test/matchers.rb +76 -0
- data/lib/dumbo/test/regression_helper.rb +20 -0
- data/lib/dumbo/test/silence_unknown_oid.rb +12 -0
- data/lib/dumbo/type.rb +3 -4
- data/lib/dumbo/version.rb +1 -1
- data/spec/aggregate_spec.rb +9 -10
- data/spec/cast_spec.rb +5 -5
- data/spec/{Makefile → dumbo_sample/Makefile} +4 -0
- data/spec/{dumbo_sample--0.0.1.sql → dumbo_sample/dumbo_sample--0.0.1.sql} +0 -0
- data/spec/{dumbo_sample--0.0.2.sql → dumbo_sample/dumbo_sample--0.0.2.sql} +0 -0
- data/spec/dumbo_sample/dumbo_sample--0.0.3.sql +7 -0
- data/spec/dumbo_sample/dumbo_sample--0.0.4.sql +13 -0
- data/spec/{dumbo_sample.control → dumbo_sample/dumbo_sample.control} +0 -0
- data/spec/dumbo_sample/src/dumbo_sample.c +13 -0
- data/spec/dumbo_sample/src/dumbo_sample.h +17 -0
- data/spec/extension_migrator_spec.rb +12 -11
- data/spec/extension_spec.rb +41 -12
- data/spec/extension_version_spec.rb +27 -0
- data/spec/operator_spec.rb +6 -7
- data/spec/spec_helper.rb +15 -9
- data/spec/support/extension_helper.rb +31 -0
- data/spec/support/silence_unknown_oid.rb +12 -0
- data/spec/type_spec.rb +59 -55
- data/template/Rakefile +6 -6
- data/template/spec/sample_spec.rb.erb +1 -1
- metadata +33 -31
- data/config/database.yml +0 -31
- data/lib/tasks/db.rake +0 -52
- data/lib/tasks/dumbo.rake +0 -23
- data/spec/support/sql_helper.rb +0 -23
data/lib/dumbo/rake_task.rb
CHANGED
@@ -1,24 +1,26 @@
|
|
1
|
-
require
|
1
|
+
require 'rake'
|
2
2
|
require 'rake/tasklib'
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'erubis'
|
4
|
+
require 'pathname'
|
5
5
|
require 'yaml'
|
6
6
|
require 'logger'
|
7
7
|
require 'active_record'
|
8
|
-
require
|
9
|
-
require
|
8
|
+
require 'dumbo/extension'
|
9
|
+
require 'dumbo/dependency_resolver'
|
10
|
+
require 'rspec/core'
|
10
11
|
|
11
12
|
module Dumbo
|
12
13
|
class RakeTask < ::Rake::TaskLib
|
13
14
|
attr_accessor :name
|
14
15
|
|
15
16
|
def initialize(name = 'dumbo')
|
16
|
-
|
17
17
|
@name = name
|
18
18
|
|
19
19
|
namespace name do
|
20
|
+
Dumbo::DbTask.new(:db)
|
21
|
+
|
20
22
|
desc 'creates and installs extension'
|
21
|
-
task :
|
23
|
+
task all: [:src, Extension.file_name, :install]
|
22
24
|
|
23
25
|
desc 'installs the extension'
|
24
26
|
task :install do
|
@@ -26,69 +28,83 @@ module Dumbo
|
|
26
28
|
end
|
27
29
|
|
28
30
|
desc 'concatenates files'
|
29
|
-
file
|
31
|
+
file Extension.file_name => file_list do |t|
|
30
32
|
sql = t.prerequisites.map do |file|
|
31
|
-
["--source file #{file}"] + get_sql(file) + [
|
33
|
+
["--source file #{file}"] + get_sql(file) + [' ']
|
32
34
|
end.flatten
|
33
35
|
concatenate sql, t.name
|
34
36
|
end
|
35
37
|
|
38
|
+
desc 'prepare source files'
|
39
|
+
task :src do
|
40
|
+
Dir.glob('src/**/*.erb').each do |file|
|
41
|
+
src = convert_template(file)
|
42
|
+
out = Pathname.new(file).sub_ext('')
|
43
|
+
File.open(out.to_s, 'w') do |f|
|
44
|
+
f.puts src.join("\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
36
49
|
desc 'creates migration files for the last two versions'
|
37
50
|
task :migrations do
|
38
|
-
old_version, new_version =
|
51
|
+
old_version, new_version = Extension.versions.last(2).map(&:to_s)
|
52
|
+
|
39
53
|
if new_version
|
40
|
-
|
54
|
+
ExtensionMigrator.new(Extension.name, old_version, new_version)
|
55
|
+
.create
|
41
56
|
end
|
42
57
|
end
|
43
58
|
|
44
|
-
desc '
|
59
|
+
desc 'upgrate .control file to a new version'
|
45
60
|
task :new_version, :level do |t, args|
|
46
|
-
args.with_defaults(:
|
47
|
-
|
48
|
-
|
61
|
+
args.with_defaults(level: 'patch')
|
62
|
+
|
63
|
+
v = new_version(args[:level])
|
64
|
+
Extension.version!(v)
|
49
65
|
|
50
66
|
Rake::Task["#{name}:all"].invoke
|
51
67
|
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def set_version(new_version)
|
56
|
-
content = File.read("#{extension}.control")
|
57
|
-
new_content = content.gsub(version, new_version)
|
58
|
-
File.open("#{extension}.control", "w") {|file| file.puts new_content}
|
59
|
-
end
|
60
68
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
69
|
+
namespace :test do
|
70
|
+
desc 'creates regression tests from specs and runs them'
|
71
|
+
task regression: ['all', 'db:test:prepare'] do
|
72
|
+
ENV['DUMBO_REGRESSION'] = 'true'
|
73
|
+
RSpec::Core::RakeTask.new(:spec).run_task(false)
|
74
|
+
|
75
|
+
if $?.success?
|
76
|
+
test_files = Rake::FileList.new("test/sql/**/*.sql")
|
77
|
+
out_files = test_files.pathmap("%{^test/sql/,test/expected/}X.out")
|
78
|
+
out_files.each{|f| FileUtils.touch(f)}
|
79
|
+
system('make installcheck &> /dev/null')
|
80
|
+
|
81
|
+
out_files.pathmap("%{^test/expected/,results/}p").each do |f|
|
82
|
+
FileUtils.cp(f,'test/expected/')
|
83
|
+
File.delete(f)
|
84
|
+
end
|
85
|
+
|
86
|
+
system('make installcheck')
|
87
|
+
else
|
88
|
+
Dir.glob('test/*').each{|f| File.delete(f)}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
77
93
|
end
|
78
94
|
|
79
|
-
def
|
80
|
-
|
95
|
+
def new_version(level = :patch)
|
96
|
+
ExtensionVersion.new_from_string(Extension.version).bump(level).to_s
|
81
97
|
end
|
82
98
|
|
83
|
-
|
99
|
+
# source sql file list
|
84
100
|
def file_list
|
85
|
-
|
101
|
+
DependencyResolver.new(Dir.glob('sql/**/*.{sql,erb}')).resolve
|
86
102
|
end
|
87
103
|
|
88
104
|
def concatenate(lines, target)
|
89
|
-
File.open(target,'w') do |f|
|
105
|
+
File.open(target, 'w') do |f|
|
90
106
|
lines.each do |line|
|
91
|
-
f.puts line unless line =~
|
107
|
+
f.puts line unless line =~ DependencyResolver.depends_pattern
|
92
108
|
end
|
93
109
|
end
|
94
110
|
end
|
@@ -109,13 +125,7 @@ module Dumbo
|
|
109
125
|
end
|
110
126
|
|
111
127
|
def get_bindings(file)
|
112
|
-
|
113
|
-
yaml = Pathname.new('config').join(base)
|
114
|
-
if yaml.exist?
|
115
|
-
YAML.load_file(yaml)
|
116
|
-
else
|
117
|
-
{}
|
118
|
-
end
|
128
|
+
BindingLoader.new(file).load
|
119
129
|
end
|
120
130
|
end
|
121
|
-
end
|
131
|
+
end
|
data/lib/dumbo/range_type.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Dumbo
|
2
2
|
class RangeType < Type
|
3
3
|
attr_accessor :subtype, :subtype_opclass, :collation,
|
4
|
-
:canonical
|
4
|
+
:canonical, :subtype_diff
|
5
5
|
|
6
6
|
def load_attributes
|
7
7
|
super
|
@@ -19,19 +19,16 @@ module Dumbo
|
|
19
19
|
WHERE rngtypid=#{oid}
|
20
20
|
SQL
|
21
21
|
|
22
|
-
result.first.each do |k,v|
|
23
|
-
send("#{k}=",v) rescue nil
|
22
|
+
result.first.each do |k, v|
|
23
|
+
send("#{k}=", v) rescue nil
|
24
24
|
end
|
25
25
|
result.first
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
28
|
def to_sql
|
32
|
-
attr_str = [:subtype, :subtype_opclass, :collation, :canonical
|
29
|
+
attr_str = [:subtype, :subtype_opclass, :collation, :canonical, :subtype_diff].map do |a|
|
33
30
|
[a, public_send(a)]
|
34
|
-
end.select{|k,v| v && v != '-' }.map{|k,v| "#{k.upcase}=#{v}"}.join(",\n ")
|
31
|
+
end.select { |k, v| v && v != '-' }.map { |k, v| "#{k.upcase}=#{v}" }.join(",\n ")
|
35
32
|
|
36
33
|
<<-SQL.gsub(/^ {6}/, '')
|
37
34
|
CREATE TYPE #{name} AS RANGE (
|
@@ -40,4 +37,4 @@ module Dumbo
|
|
40
37
|
SQL
|
41
38
|
end
|
42
39
|
end
|
43
|
-
end
|
40
|
+
end
|
data/lib/dumbo/test.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
module Dumbo
|
3
|
+
module Test
|
4
|
+
class Fixture
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_reader :fixtures
|
8
|
+
|
9
|
+
def self.fixtures
|
10
|
+
instance.fixtures
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@fixtures = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def eval_fixture(file,contents=nil)
|
18
|
+
contents ||= File.read(file.to_s)
|
19
|
+
instance_eval(contents)
|
20
|
+
@fixtures
|
21
|
+
rescue SyntaxError => e
|
22
|
+
syntax_msg = e.message.gsub("#{file}:", 'on line ')
|
23
|
+
raise "Fixture syntax error #{syntax_msg}"
|
24
|
+
rescue ScriptError, RegexpError, NameError, ArgumentError => e
|
25
|
+
e.backtrace[0] = "#{e.backtrace[0]}: #{e.message} (#{e.class})"
|
26
|
+
puts e.backtrace.join("\n ")
|
27
|
+
raise "There was an error in your Fixture," \
|
28
|
+
+ e.message
|
29
|
+
end
|
30
|
+
|
31
|
+
def fixture(name, *args)
|
32
|
+
opts = extract_options!(args)
|
33
|
+
table_name = args.first || name
|
34
|
+
|
35
|
+
@fixtures[name] = [table_name, opts]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def extract_options!(arr)
|
42
|
+
if arr.last.is_a? Hash
|
43
|
+
arr.pop
|
44
|
+
else
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'dumbo/test/fixture'
|
3
|
+
module Dumbo
|
4
|
+
module Test
|
5
|
+
module Helper
|
6
|
+
class Fixture
|
7
|
+
def initialize(table_name, values)
|
8
|
+
@table_name, @values = table_name, values
|
9
|
+
end
|
10
|
+
|
11
|
+
def values
|
12
|
+
(read_fixture.last || {}).merge(@values)
|
13
|
+
end
|
14
|
+
|
15
|
+
def table_name
|
16
|
+
read_fixture.first || @table_name
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def read_fixture
|
22
|
+
@fixtures ||= (Dumbo::Test::Fixture.fixtures[@table_name] || [])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class SqlLogger < Logger
|
27
|
+
def format_message(severity, timestamp, progname, msg)
|
28
|
+
"#{msg.gsub(/\e\[(\d+)m/, '').gsub(/.*?\(.*?ms\)/,'').gsub(/^ +/,'')};\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(*args)
|
32
|
+
return unless args.first == DEBUG
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def install_extension
|
39
|
+
query "CREATE EXTENSION #{Dumbo::Extension.new.name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def query(sql)
|
43
|
+
ActiveRecord::Base.connection.select_all(sql, 'SQL', [])
|
44
|
+
end
|
45
|
+
|
46
|
+
def create(table_name, values)
|
47
|
+
fix = Fixture.new(table_name, values)
|
48
|
+
table_name, values = fix.table_name, fix.values
|
49
|
+
|
50
|
+
ActiveRecord::Base.connection.insert_fixture(values, table_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_list(num, table_name, &block)
|
54
|
+
num.times do |i|
|
55
|
+
block_val = block.call(i)
|
56
|
+
fix = Fixture.new(table_name, block_val)
|
57
|
+
table_name, values = fix.table_name, fix.values
|
58
|
+
|
59
|
+
ActiveRecord::Base.connection.insert_fixture(values, table_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Dumbo
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
# test a query result against an expectation
|
5
|
+
# e.g.
|
6
|
+
# query("SELECT COUNT(*) FROM users").should match '3'
|
7
|
+
# query("SELECT id, name FROM users").should match ['1', 'foo'] ,['2', 'bar'] ,['3', 'baz']
|
8
|
+
# query("SELECT id, name FROM users").should match_with_header ['id', 'name'], ['1', 'foo'] ,['2', 'bar'] ,['3', 'baz']
|
9
|
+
# query("SELECT id, name FROM users ORDER BY id").should match_ordered ['id', 'name'], ['1', 'foo'] ,['2', 'bar'] ,['3', 'baz']
|
10
|
+
|
11
|
+
def match(*expected)
|
12
|
+
return super if expected.first.is_a? Regexp
|
13
|
+
QueryMatcher.new(flat_expected(expected))
|
14
|
+
end
|
15
|
+
|
16
|
+
def match_with_header(*expected)
|
17
|
+
QueryMatcher.new(flat_expected(expected), header: true)
|
18
|
+
end
|
19
|
+
|
20
|
+
def match_ordered(*expected)
|
21
|
+
QueryMatcher.new(flat_expected(expected), ordered: true )
|
22
|
+
end
|
23
|
+
|
24
|
+
def flat_expected(expected)
|
25
|
+
expected.size == 1 ? expected.first.to_s : expected.map(&:to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
class QueryMatcher < RSpec::Matchers::BuiltIn::ContainExactly
|
29
|
+
attr_reader :actual, :expected, :options
|
30
|
+
|
31
|
+
def initialize(expected = UNDEFINED, options={})
|
32
|
+
@expected = expected unless UNDEFINED.equal?(expected)
|
33
|
+
@options = options
|
34
|
+
end
|
35
|
+
|
36
|
+
def matches?(actual)
|
37
|
+
@actual = actual
|
38
|
+
convert_actual
|
39
|
+
convert_expected
|
40
|
+
match = match(expected, self.actual)
|
41
|
+
options[:ordered] ? expected == self.actual : match
|
42
|
+
end
|
43
|
+
|
44
|
+
def failure_message_for_should
|
45
|
+
if @missing_items.empty? && @extra_items.empty?
|
46
|
+
message = "actual collection is in the wrong order\n"
|
47
|
+
message += "expected collection contained: #{expected.inspect}\n"
|
48
|
+
message += "actual collection contained: #{actual.inspect}\n"
|
49
|
+
message
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def convert_expected
|
57
|
+
@expected = [expected] unless expected.is_a?(Array)
|
58
|
+
end
|
59
|
+
|
60
|
+
def convert_actual
|
61
|
+
@actual = if options[:header]
|
62
|
+
[actual.columns] + actual.rows
|
63
|
+
else
|
64
|
+
case actual.rows.size
|
65
|
+
when 0
|
66
|
+
[]
|
67
|
+
when 1
|
68
|
+
actual.rows.first
|
69
|
+
else
|
70
|
+
actual.rows
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.before(:all) do |e|
|
3
|
+
path = self.class.metadata[:file_path]
|
4
|
+
test_file = Pathname.new(path).basename.sub_ext('.sql').sub('_spec','')
|
5
|
+
test_path = test_file.realdirpath File.expand_path('test/sql')
|
6
|
+
FileUtils.touch(test_path)
|
7
|
+
|
8
|
+
ActiveRecord::Base.logger = Dumbo::Test::Helper::SqlLogger.new(test_path)
|
9
|
+
ActiveRecord::Base.logger.level = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
config.before(:suite) do |s|
|
13
|
+
FileUtils.rm_r Dir.glob('test/sql/*'), force: true
|
14
|
+
end
|
15
|
+
|
16
|
+
config.before(:each) do |example|
|
17
|
+
ActiveRecord::Base.logger.debug "-- " + example.metadata[:full_description]
|
18
|
+
ActiveRecord::Base.logger.debug "-- " + example.metadata[:location]
|
19
|
+
end
|
20
|
+
end
|