dumbo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|