dumbo 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile +1 -1
  4. data/bin/dumbo +65 -24
  5. data/config/boot.rb +2 -3
  6. data/dumbo.gemspec +1 -2
  7. data/lib/dumbo.rb +17 -16
  8. data/lib/dumbo/aggregate.rb +3 -5
  9. data/lib/dumbo/base_type.rb +17 -17
  10. data/lib/dumbo/binding_loader.rb +50 -0
  11. data/lib/dumbo/cast.rb +5 -5
  12. data/lib/dumbo/composite_type.rb +4 -4
  13. data/lib/dumbo/db_task.rb +6 -5
  14. data/lib/dumbo/dependency_resolver.rb +17 -18
  15. data/lib/dumbo/enum_type.rb +3 -4
  16. data/lib/dumbo/extension.rb +64 -20
  17. data/lib/dumbo/extension_migrator.rb +11 -11
  18. data/lib/dumbo/extension_version.rb +29 -11
  19. data/lib/dumbo/function.rb +21 -21
  20. data/lib/dumbo/operator.rb +4 -6
  21. data/lib/dumbo/pg_object.rb +17 -21
  22. data/lib/dumbo/rake_task.rb +63 -53
  23. data/lib/dumbo/range_type.rb +6 -9
  24. data/lib/dumbo/test.rb +8 -0
  25. data/lib/dumbo/test/fixture.rb +51 -0
  26. data/lib/dumbo/test/helper.rb +64 -0
  27. data/lib/dumbo/test/matchers.rb +76 -0
  28. data/lib/dumbo/test/regression_helper.rb +20 -0
  29. data/lib/dumbo/test/silence_unknown_oid.rb +12 -0
  30. data/lib/dumbo/type.rb +3 -4
  31. data/lib/dumbo/version.rb +1 -1
  32. data/spec/aggregate_spec.rb +9 -10
  33. data/spec/cast_spec.rb +5 -5
  34. data/spec/{Makefile → dumbo_sample/Makefile} +4 -0
  35. data/spec/{dumbo_sample--0.0.1.sql → dumbo_sample/dumbo_sample--0.0.1.sql} +0 -0
  36. data/spec/{dumbo_sample--0.0.2.sql → dumbo_sample/dumbo_sample--0.0.2.sql} +0 -0
  37. data/spec/dumbo_sample/dumbo_sample--0.0.3.sql +7 -0
  38. data/spec/dumbo_sample/dumbo_sample--0.0.4.sql +13 -0
  39. data/spec/{dumbo_sample.control → dumbo_sample/dumbo_sample.control} +0 -0
  40. data/spec/dumbo_sample/src/dumbo_sample.c +13 -0
  41. data/spec/dumbo_sample/src/dumbo_sample.h +17 -0
  42. data/spec/extension_migrator_spec.rb +12 -11
  43. data/spec/extension_spec.rb +41 -12
  44. data/spec/extension_version_spec.rb +27 -0
  45. data/spec/operator_spec.rb +6 -7
  46. data/spec/spec_helper.rb +15 -9
  47. data/spec/support/extension_helper.rb +31 -0
  48. data/spec/support/silence_unknown_oid.rb +12 -0
  49. data/spec/type_spec.rb +59 -55
  50. data/template/Rakefile +6 -6
  51. data/template/spec/sample_spec.rb.erb +1 -1
  52. metadata +33 -31
  53. data/config/database.yml +0 -31
  54. data/lib/tasks/db.rake +0 -52
  55. data/lib/tasks/dumbo.rake +0 -23
  56. data/spec/support/sql_helper.rb +0 -23
@@ -1,24 +1,26 @@
1
- require "rake"
1
+ require 'rake'
2
2
  require 'rake/tasklib'
3
- require "erubis"
4
- require "pathname"
3
+ require 'erubis'
4
+ require 'pathname'
5
5
  require 'yaml'
6
6
  require 'logger'
7
7
  require 'active_record'
8
- require "dumbo/extension"
9
- require "dumbo/dependency_resolver"
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 :all => ["#{extension}--#{version}.sql", :install]
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 "#{extension}--#{version}.sql" => file_list do |t|
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 = Dumbo::Extension.new.available_versions.last(2).map(&:to_s)
51
+ old_version, new_version = Extension.versions.last(2).map(&:to_s)
52
+
39
53
  if new_version
40
- Dumbo::ExtensionMigrator.new(Dumbo::Extension.new.name, old_version, new_version).create
54
+ ExtensionMigrator.new(Extension.name, old_version, new_version)
55
+ .create
41
56
  end
42
57
  end
43
58
 
44
- desc 'release a new version'
59
+ desc 'upgrate .control file to a new version'
45
60
  task :new_version, :level do |t, args|
46
- args.with_defaults(:level => 'patch')
47
- v = version_bump args[:level]
48
- set_version v
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
- def version_bump(level='patch')
62
- levels = {'patch' => 2, 'minor' => 1, 'major' => 0}
63
- parts = version.split('.').map(&:to_i)
64
- l = levels[level]
65
- parts[l] += 1
66
- (l+1..2).each {|l| parts[l]=0}
67
-
68
- parts.join('.')
69
- end
70
-
71
- def version
72
- Dumbo::Extension.new.version
73
- end
74
-
75
- def extension
76
- Dumbo::Extension.new.name
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 available_versions
80
- Dumbo::Extension.new.name.available_versions
95
+ def new_version(level = :patch)
96
+ ExtensionVersion.new_from_string(Extension.version).bump(level).to_s
81
97
  end
82
98
 
83
- # source sql file list
99
+ # source sql file list
84
100
  def file_list
85
- Dumbo::DependencyResolver.new(Dir.glob("sql/**/*.{sql,erb}")).resolve
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 =~ Dumbo::DependencyResolver.depends_pattern
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
- base = Pathname.new(file).sub_ext('.yml').basename
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
@@ -1,7 +1,7 @@
1
1
  module Dumbo
2
2
  class RangeType < Type
3
3
  attr_accessor :subtype, :subtype_opclass, :collation,
4
- :canonical,:subtype_diff
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,:subtype_diff].map do |a|
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
@@ -0,0 +1,8 @@
1
+ require 'dumbo/test/fixture'
2
+ require 'dumbo/test/helper'
3
+ require 'dumbo/test/matchers'
4
+
5
+ module Dumbo
6
+ module Test
7
+ end
8
+ end
@@ -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
@@ -0,0 +1,12 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter
4
+ module DatabaseStatements
5
+ def warn(msg)
6
+ return if msg =~ /^unknown OID/
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end