dumbo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +61 -0
  6. data/Rakefile +9 -0
  7. data/bin/dumbo +58 -0
  8. data/config/boot.rb +15 -0
  9. data/config/database.yml +31 -0
  10. data/dumbo.gemspec +31 -0
  11. data/lib/dumbo.rb +21 -0
  12. data/lib/dumbo/aggregate.rb +57 -0
  13. data/lib/dumbo/base_type.rb +71 -0
  14. data/lib/dumbo/cast.rb +49 -0
  15. data/lib/dumbo/composite_type.rb +31 -0
  16. data/lib/dumbo/db_task.rb +57 -0
  17. data/lib/dumbo/dependency_resolver.rb +105 -0
  18. data/lib/dumbo/enum_type.rb +28 -0
  19. data/lib/dumbo/extension.rb +73 -0
  20. data/lib/dumbo/extension_migrator.rb +66 -0
  21. data/lib/dumbo/extension_version.rb +25 -0
  22. data/lib/dumbo/function.rb +101 -0
  23. data/lib/dumbo/operator.rb +74 -0
  24. data/lib/dumbo/pg_object.rb +80 -0
  25. data/lib/dumbo/rake_task.rb +121 -0
  26. data/lib/dumbo/range_type.rb +43 -0
  27. data/lib/dumbo/type.rb +31 -0
  28. data/lib/dumbo/version.rb +3 -0
  29. data/lib/tasks/db.rake +52 -0
  30. data/lib/tasks/dumbo.rake +23 -0
  31. data/spec/Makefile +6 -0
  32. data/spec/aggregate_spec.rb +41 -0
  33. data/spec/cast_spec.rb +20 -0
  34. data/spec/dumbo_sample--0.0.1.sql +5 -0
  35. data/spec/dumbo_sample--0.0.2.sql +5 -0
  36. data/spec/dumbo_sample.control +5 -0
  37. data/spec/extension_migrator_spec.rb +40 -0
  38. data/spec/extension_spec.rb +19 -0
  39. data/spec/operator_spec.rb +42 -0
  40. data/spec/spec_helper.rb +28 -0
  41. data/spec/support/sql_helper.rb +23 -0
  42. data/spec/type_spec.rb +95 -0
  43. data/template/Gemfile +3 -0
  44. data/template/Makefile.erb +6 -0
  45. data/template/Rakefile +15 -0
  46. data/template/config/database.yml.erb +31 -0
  47. data/template/spec/sample_spec.rb.erb +13 -0
  48. data/template/sql/sample.sql +5 -0
  49. 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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Dumbo
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ #http://blog.pgxn.org/post/4783001135/extension-makefiles pg makefiles
2
+ EXTENSION = dumbo_sample
3
+ PG_CONFIG ?= pg_config
4
+ DATA = $(wildcard *--*.sql)
5
+ PGXS := $(shell $(PG_CONFIG) --pgxs)
6
+ include $(PGXS)
@@ -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