dumbo 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +61 -0
- data/Rakefile +9 -0
- data/bin/dumbo +58 -0
- data/config/boot.rb +15 -0
- data/config/database.yml +31 -0
- data/dumbo.gemspec +31 -0
- data/lib/dumbo.rb +21 -0
- data/lib/dumbo/aggregate.rb +57 -0
- data/lib/dumbo/base_type.rb +71 -0
- data/lib/dumbo/cast.rb +49 -0
- data/lib/dumbo/composite_type.rb +31 -0
- data/lib/dumbo/db_task.rb +57 -0
- data/lib/dumbo/dependency_resolver.rb +105 -0
- data/lib/dumbo/enum_type.rb +28 -0
- data/lib/dumbo/extension.rb +73 -0
- data/lib/dumbo/extension_migrator.rb +66 -0
- data/lib/dumbo/extension_version.rb +25 -0
- data/lib/dumbo/function.rb +101 -0
- data/lib/dumbo/operator.rb +74 -0
- data/lib/dumbo/pg_object.rb +80 -0
- data/lib/dumbo/rake_task.rb +121 -0
- data/lib/dumbo/range_type.rb +43 -0
- data/lib/dumbo/type.rb +31 -0
- data/lib/dumbo/version.rb +3 -0
- data/lib/tasks/db.rake +52 -0
- data/lib/tasks/dumbo.rake +23 -0
- data/spec/Makefile +6 -0
- data/spec/aggregate_spec.rb +41 -0
- data/spec/cast_spec.rb +20 -0
- data/spec/dumbo_sample--0.0.1.sql +5 -0
- data/spec/dumbo_sample--0.0.2.sql +5 -0
- data/spec/dumbo_sample.control +5 -0
- data/spec/extension_migrator_spec.rb +40 -0
- data/spec/extension_spec.rb +19 -0
- data/spec/operator_spec.rb +42 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/sql_helper.rb +23 -0
- data/spec/type_spec.rb +95 -0
- data/template/Gemfile +3 -0
- data/template/Makefile.erb +6 -0
- data/template/Rakefile +15 -0
- data/template/config/database.yml.erb +31 -0
- data/template/spec/sample_spec.rb.erb +13 -0
- data/template/sql/sample.sql +5 -0
- metadata +230 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Dumbo
|
2
|
+
class Operator < PgObject
|
3
|
+
attr_accessor :name,
|
4
|
+
:kind,
|
5
|
+
:hashes,
|
6
|
+
:merges,
|
7
|
+
:leftarg,
|
8
|
+
:rightarg,
|
9
|
+
:result_type,
|
10
|
+
:commutator,
|
11
|
+
:negator,
|
12
|
+
:function_name,
|
13
|
+
:join,
|
14
|
+
:restrict
|
15
|
+
|
16
|
+
identfied_by :name, :leftarg, :rightarg
|
17
|
+
|
18
|
+
def load_attributes
|
19
|
+
|
20
|
+
result = execute <<-SQL
|
21
|
+
SELECT
|
22
|
+
op.oprname AS name,
|
23
|
+
op.oprkind AS kind,
|
24
|
+
op.oprcanhash AS hashes,
|
25
|
+
op.oprcanmerge AS merges,
|
26
|
+
lt.typname AS leftarg,
|
27
|
+
rt.typname AS rightarg,
|
28
|
+
et.typname AS result_type,
|
29
|
+
co.oprname AS commutator,
|
30
|
+
ne.oprname AS negator,
|
31
|
+
op.oprcode AS function_name,
|
32
|
+
op.oprjoin AS join,
|
33
|
+
op.oprrest AS restrict,
|
34
|
+
description
|
35
|
+
FROM pg_operator op
|
36
|
+
LEFT OUTER JOIN pg_type lt ON lt.oid=op.oprleft
|
37
|
+
LEFT OUTER JOIN pg_type rt ON rt.oid=op.oprright
|
38
|
+
JOIN pg_type et on et.oid=op.oprresult
|
39
|
+
LEFT OUTER JOIN pg_operator co ON co.oid=op.oprcom
|
40
|
+
LEFT OUTER JOIN pg_operator ne ON ne.oid=op.oprnegate
|
41
|
+
LEFT OUTER JOIN pg_description des ON des.objoid=op.oid
|
42
|
+
WHERE op.oid = #{oid}
|
43
|
+
SQL
|
44
|
+
|
45
|
+
result.first.each do |k,v|
|
46
|
+
send("#{k}=",v) rescue nil
|
47
|
+
end
|
48
|
+
|
49
|
+
result.first
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_sql
|
54
|
+
attrs = [:leftarg, :rightarg, :commutator, :negator, :restrict, :join].inject([]) do |mem, attr|
|
55
|
+
mem << "#{attr.to_s.upcase} = #{public_send(attr)}" if public_send(attr)
|
56
|
+
mem
|
57
|
+
end
|
58
|
+
atttr_str = attrs.join(",\n ")
|
59
|
+
attrs << ",\n HASHES" if hashes
|
60
|
+
attrs << ",\n MERGES" if merges
|
61
|
+
|
62
|
+
<<-SQL.gsub(/^ {6}/, '')
|
63
|
+
CREATE OPERATOR #{name} (
|
64
|
+
PROCEDURE = #{function_name},
|
65
|
+
#{atttr_str}
|
66
|
+
);
|
67
|
+
SQL
|
68
|
+
end
|
69
|
+
|
70
|
+
def drop
|
71
|
+
"DROP OPERATOR #{name};"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module Dumbo
|
4
|
+
class PgObject
|
5
|
+
attr_reader :oid
|
6
|
+
class_attribute :identifier
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def identfied_by(*args)
|
10
|
+
self.identifier = args
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(oid)
|
15
|
+
@oid = oid
|
16
|
+
load_attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
def identify
|
20
|
+
identifier.map{|a| public_send a}
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(type=nil)
|
24
|
+
case type
|
25
|
+
when 'function', 'pg_proc'
|
26
|
+
Function.new(oid).get
|
27
|
+
when 'cast', 'pg_cast'
|
28
|
+
Cast.new(oid).get
|
29
|
+
when 'operator', 'pg_operator'
|
30
|
+
Operator.new(oid).get
|
31
|
+
when 'type', 'pg_type'
|
32
|
+
Type.new(oid).get
|
33
|
+
else
|
34
|
+
self.load_attributes
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_attributes
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def upgrade(other)
|
44
|
+
return self.to_sql if other.nil?
|
45
|
+
|
46
|
+
if other.identify != self.identify
|
47
|
+
raise "Not the Same Objects!"
|
48
|
+
end
|
49
|
+
|
50
|
+
if other.to_sql != self.to_sql
|
51
|
+
<<-SQL.gsub(/^ {8}/, '')
|
52
|
+
#{self.drop}
|
53
|
+
#{self.to_sql}
|
54
|
+
SQL
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def downgrade(other)
|
61
|
+
return self.drop if other.nil?
|
62
|
+
|
63
|
+
if other.identify != self.identify
|
64
|
+
raise "Not the Same Objects!"
|
65
|
+
end
|
66
|
+
|
67
|
+
if other.to_sql != self.to_sql
|
68
|
+
<<-SQL.gsub(/^ {8}/, '')
|
69
|
+
#{self.drop}
|
70
|
+
#{other.to_sql}
|
71
|
+
SQL
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute(sql)
|
76
|
+
ActiveRecord::Base.connection.execute(sql)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require "rake"
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require "erubis"
|
4
|
+
require "pathname"
|
5
|
+
require 'yaml'
|
6
|
+
require 'logger'
|
7
|
+
require 'active_record'
|
8
|
+
require "dumbo/extension"
|
9
|
+
require "dumbo/dependency_resolver"
|
10
|
+
|
11
|
+
module Dumbo
|
12
|
+
class RakeTask < ::Rake::TaskLib
|
13
|
+
attr_accessor :name
|
14
|
+
|
15
|
+
def initialize(name = 'dumbo')
|
16
|
+
|
17
|
+
@name = name
|
18
|
+
|
19
|
+
namespace name do
|
20
|
+
desc 'creates and installs extension'
|
21
|
+
task :all => ["#{extension}--#{version}.sql", :install]
|
22
|
+
|
23
|
+
desc 'installs the extension'
|
24
|
+
task :install do
|
25
|
+
system('make clean && make && make install')
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'concatenates files'
|
29
|
+
file "#{extension}--#{version}.sql" => file_list do |t|
|
30
|
+
sql = t.prerequisites.map do |file|
|
31
|
+
["--source file #{file}"] + get_sql(file) + [" "]
|
32
|
+
end.flatten
|
33
|
+
concatenate sql, t.name
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'creates migration files for the last two versions'
|
37
|
+
task :migrations do
|
38
|
+
old_version, new_version = Dumbo::Extension.new.available_versions.last(2).map(&:to_s)
|
39
|
+
if new_version
|
40
|
+
Dumbo::ExtensionMigrator.new(Dumbo::Extension.new.name, old_version, new_version).create
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'release a new version'
|
45
|
+
task :new_version, :level do |t, args|
|
46
|
+
args.with_defaults(:level => 'patch')
|
47
|
+
v = version_bump args[:level]
|
48
|
+
set_version v
|
49
|
+
|
50
|
+
Rake::Task["#{name}:all"].invoke
|
51
|
+
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
|
+
|
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
|
77
|
+
end
|
78
|
+
|
79
|
+
def available_versions
|
80
|
+
Dumbo::Extension.new.name.available_versions
|
81
|
+
end
|
82
|
+
|
83
|
+
# source sql file list
|
84
|
+
def file_list
|
85
|
+
Dumbo::DependencyResolver.new(Dir.glob("sql/**/*.{sql,erb}")).resolve
|
86
|
+
end
|
87
|
+
|
88
|
+
def concatenate(lines, target)
|
89
|
+
File.open(target,'w') do |f|
|
90
|
+
lines.each do |line|
|
91
|
+
f.puts line unless line =~ Dumbo::DependencyResolver.depends_pattern
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_sql(file)
|
97
|
+
ext = Pathname.new(file).extname
|
98
|
+
if ext == '.erb'
|
99
|
+
convert_template(file)
|
100
|
+
else
|
101
|
+
File.readlines(file)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def convert_template(file)
|
106
|
+
eruby = Erubis::Eruby.new(File.read(file))
|
107
|
+
bindigs = get_bindings(file)
|
108
|
+
eruby.result(bindigs).split("\n")
|
109
|
+
end
|
110
|
+
|
111
|
+
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
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Dumbo
|
2
|
+
class RangeType < Type
|
3
|
+
attr_accessor :subtype, :subtype_opclass, :collation,
|
4
|
+
:canonical,:subtype_diff
|
5
|
+
|
6
|
+
def load_attributes
|
7
|
+
super
|
8
|
+
result = execute <<-SQL
|
9
|
+
SELECT
|
10
|
+
st.typname AS subtype,
|
11
|
+
opc.opcname AS subtype_opclass,
|
12
|
+
col.collname AS collation,
|
13
|
+
rngcanonical AS canonical,
|
14
|
+
rngsubdiff AS subtype_diff
|
15
|
+
FROM pg_range
|
16
|
+
LEFT JOIN pg_type st ON st.oid=rngsubtype
|
17
|
+
LEFT JOIN pg_collation col ON col.oid=rngcollation
|
18
|
+
LEFT JOIN pg_opclass opc ON opc.oid=rngsubopc
|
19
|
+
WHERE rngtypid=#{oid}
|
20
|
+
SQL
|
21
|
+
|
22
|
+
result.first.each do |k,v|
|
23
|
+
send("#{k}=",v) rescue nil
|
24
|
+
end
|
25
|
+
result.first
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
def to_sql
|
32
|
+
attr_str = [:subtype, :subtype_opclass, :collation, :canonical,:subtype_diff].map do |a|
|
33
|
+
[a, public_send(a)]
|
34
|
+
end.select{|k,v| v && v != '-' }.map{|k,v| "#{k.upcase}=#{v}"}.join(",\n ")
|
35
|
+
|
36
|
+
<<-SQL.gsub(/^ {6}/, '')
|
37
|
+
CREATE TYPE #{name} AS RANGE (
|
38
|
+
#{attr_str}
|
39
|
+
);
|
40
|
+
SQL
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/dumbo/type.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Dumbo
|
2
|
+
class Type < PgObject
|
3
|
+
attr_accessor :name, :type, :typrelid
|
4
|
+
identfied_by :name
|
5
|
+
|
6
|
+
def load_attributes
|
7
|
+
result = execute("SELECT typname, typtype, typrelid FROM pg_type WHERE oid = #{oid}").first
|
8
|
+
@name = result['typname']
|
9
|
+
@type = result['typtype']
|
10
|
+
@typrelid = result['typrelid']
|
11
|
+
end
|
12
|
+
|
13
|
+
def get
|
14
|
+
case type
|
15
|
+
when 'c'
|
16
|
+
CompositeType.new(oid)
|
17
|
+
when 'b'
|
18
|
+
BaseType.new(oid)
|
19
|
+
when 'r'
|
20
|
+
RangeType.new(oid)
|
21
|
+
when 'e'
|
22
|
+
EnumType.new(oid)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def drop
|
27
|
+
"DROP TYPE #{name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/tasks/db.rake
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'logger'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
task environment: ['db:configure_connection' ]
|
6
|
+
|
7
|
+
task :test_env do
|
8
|
+
ENV['DUMBO_ENV'] = 'test'
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :db do
|
12
|
+
def create_database(config)
|
13
|
+
ActiveRecord::Base.establish_connection config.merge('database' => nil)
|
14
|
+
ActiveRecord::Base.connection.create_database config['database'], config
|
15
|
+
ActiveRecord::Base.establish_connection config
|
16
|
+
end
|
17
|
+
|
18
|
+
task :configuration do
|
19
|
+
@config = YAML.load_file('config/database.yml')[ENV['DUMBO_ENV']]
|
20
|
+
end
|
21
|
+
|
22
|
+
task configure_connection: :configuration do
|
23
|
+
ActiveRecord::Base.establish_connection @config
|
24
|
+
ActiveRecord::Base.logger = Logger.new STDOUT if @config['logger']
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Create the database from config/database.yml for the current ENV'
|
28
|
+
task create: :environment do
|
29
|
+
create_database @config
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Drops the database for the current ENV'
|
33
|
+
task drop: :environment do
|
34
|
+
ActiveRecord::Base.establish_connection @config.merge('database' => nil)
|
35
|
+
ActiveRecord::Base.connection.drop_database @config['database']
|
36
|
+
end
|
37
|
+
|
38
|
+
namespace :test do
|
39
|
+
task :environment do
|
40
|
+
ENV['DUMBO_ENV'] ||= 'test'
|
41
|
+
ActiveRecord::Schema.verbose = false
|
42
|
+
end
|
43
|
+
|
44
|
+
task load_structure: :environment do
|
45
|
+
filename = ENV['DB_STRUCTURE'] || File.join("db", "structure.sql")
|
46
|
+
ActiveRecord::Tasks::DatabaseTasks.structure_load(@config, filename)
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Re-create and prepare test database"
|
50
|
+
task prepare: [:environment, :drop, :create]
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
task :default => ['dumbo:all', :spec]
|
5
|
+
|
6
|
+
namespace :dumbo do
|
7
|
+
include Dumbo::RakeHelper
|
8
|
+
|
9
|
+
task :all => ["#{extension}--#{version}.sql", :install]
|
10
|
+
|
11
|
+
desc 'installs the extension'
|
12
|
+
task :install do
|
13
|
+
system('make clean && make && make install')
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'concatenates files'
|
17
|
+
file "#{extension}--#{version}.sql" => file_list do |t|
|
18
|
+
sql = t.prerequisites.map do |file|
|
19
|
+
["--source file #{file}"] + get_sql(file) + [" "]
|
20
|
+
end.flatten
|
21
|
+
concatenate sql, t.name
|
22
|
+
end
|
23
|
+
end
|
data/spec/Makefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Dumbo::Aggregate do
|
3
|
+
let(:avg) do
|
4
|
+
oid = sql("SELECT p.oid
|
5
|
+
FROM pg_proc p
|
6
|
+
JOIN pg_aggregate ag ON p.oid = ag.aggfnoid
|
7
|
+
WHERE proname='avg' AND pg_get_function_arguments(p.oid) = 'integer'",'oid').first
|
8
|
+
Dumbo::Aggregate.new(oid)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:min) do
|
12
|
+
oid = sql("SELECT p.oid
|
13
|
+
FROM pg_proc p
|
14
|
+
JOIN pg_aggregate ag ON p.oid = ag.aggfnoid
|
15
|
+
WHERE proname='min' AND pg_get_function_arguments(p.oid) = 'integer'",'oid').first
|
16
|
+
Dumbo::Aggregate.new(oid)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
it "avg should have a sql representation" do
|
21
|
+
avg.to_sql.should eq <<-SQL.gsub(/^ {6}/, '')
|
22
|
+
CREATE AGGREGATE avg(integer) (
|
23
|
+
SFUNC = int4_avg_accum,
|
24
|
+
STYPE = int8[],
|
25
|
+
FINALFUNC = int8_avg,
|
26
|
+
INITCOND = '{0,0}'
|
27
|
+
);
|
28
|
+
SQL
|
29
|
+
end
|
30
|
+
|
31
|
+
it "min should have a sql representation" do
|
32
|
+
min.to_sql.should eq <<-SQL.gsub(/^ {6}/, '')
|
33
|
+
CREATE AGGREGATE min(integer) (
|
34
|
+
SFUNC = int4smaller,
|
35
|
+
STYPE = int4,
|
36
|
+
SORTOP = <
|
37
|
+
);
|
38
|
+
SQL
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|