dm-reflection 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +25 -0
- data/LICENSE +21 -0
- data/README.rdoc +77 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/dm-reflection.gemspec +79 -0
- data/lib/dm-reflection/adapters/mysql.rb +95 -0
- data/lib/dm-reflection/adapters/persevere.rb +98 -0
- data/lib/dm-reflection/adapters/postgres.rb +126 -0
- data/lib/dm-reflection/adapters/sqlite3.rb +85 -0
- data/lib/dm-reflection/builders/source_builder.rb +326 -0
- data/lib/dm-reflection/reflection.rb +73 -0
- data/lib/dm-reflection/version.rb +5 -0
- data/lib/dm-reflection.rb +6 -0
- data/spec/persevere_reflection_spec.rb +86 -0
- data/spec/rcov.opts +6 -0
- data/spec/reflection_spec.rb +108 -0
- data/spec/source_builder_spec.rb +130 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +41 -0
- data/tasks/changelog.rake +20 -0
- data/tasks/ci.rake +1 -0
- data/tasks/metrics.rake +36 -0
- data/tasks/spec.rake +25 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +131 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## PROJECT::GENERAL
|
17
|
+
coverage
|
18
|
+
rdoc
|
19
|
+
pkg
|
20
|
+
|
21
|
+
## PROJECT::SPECIFIC
|
22
|
+
|
23
|
+
## Yard
|
24
|
+
.yardoc
|
25
|
+
measurements/report.txt
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2009 Martin Gamsjaeger (snusnu)
|
2
|
+
Copyright (c) 2010 Montana State University
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
= dm-reflection
|
2
|
+
|
3
|
+
For now, this gem only allows you to print the ruby source for any DataMapper::Model, taking all property and relationship definitions into account. However, the plan is to be able to reflect on an existing relational database schema and export the corresponding datamapper model definitions to ruby source files. Think of it as active_record and annotate_models combined to do something useful with legacy databases.
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'dm-core'
|
7
|
+
require 'dm-reflection'
|
8
|
+
|
9
|
+
class Project
|
10
|
+
|
11
|
+
include DataMapper::Resource
|
12
|
+
|
13
|
+
property :id, Serial
|
14
|
+
|
15
|
+
property :name, String, :required => true, :length => 200
|
16
|
+
property :created_at, DateTime
|
17
|
+
property :updated_at, DateTime
|
18
|
+
|
19
|
+
has 0..n, :project_tasks
|
20
|
+
has 0..n, :tasks, :through => :project_tasks
|
21
|
+
has 0..n, :people, :through => Resource
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
Project.to_ruby # produces this nicely formatted ruby code
|
26
|
+
|
27
|
+
<<-RUBY
|
28
|
+
class Project
|
29
|
+
|
30
|
+
include DataMapper::Resource
|
31
|
+
|
32
|
+
property :id, Serial
|
33
|
+
|
34
|
+
property :name, String, :required => true, :length => 200
|
35
|
+
property :created_at, DateTime
|
36
|
+
property :updated_at, DateTime
|
37
|
+
|
38
|
+
has 0..n, :project_tasks
|
39
|
+
has 0..n, :tasks, :through => :project_tasks
|
40
|
+
has 0..n, :people, :through => Resource
|
41
|
+
|
42
|
+
end
|
43
|
+
RUBY
|
44
|
+
|
45
|
+
Also, you can create models from a repository by doing the following:
|
46
|
+
|
47
|
+
require 'dm-reflection'
|
48
|
+
models = DataMapper::Reflection.reflect(:default)
|
49
|
+
|
50
|
+
== Note on Patches/Pull Requests
|
51
|
+
|
52
|
+
* Fork the project.
|
53
|
+
* Make your feature addition or bug fix.
|
54
|
+
* Add tests for it. This is important so I don't break it in a
|
55
|
+
future version unintentionally.
|
56
|
+
* Commit, do not mess with rakefile, version, or history.
|
57
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
58
|
+
* Send me a pull request. Bonus points for topic branches.
|
59
|
+
|
60
|
+
== Future Work
|
61
|
+
|
62
|
+
* Support Associations/Relationships
|
63
|
+
* Support Single Table Inheritance
|
64
|
+
* Ensure Round tripping is functional
|
65
|
+
** Create model M in DataMapper
|
66
|
+
** Migrate M to a repository
|
67
|
+
** Reflect M' from the repository
|
68
|
+
** M should == M'
|
69
|
+
|
70
|
+
* Having round tripping provably working is the first goal
|
71
|
+
* Legacy reflection is something to make work after round tripping (because the assumptions DM makes won't always hold.)
|
72
|
+
|
73
|
+
== Copyright
|
74
|
+
|
75
|
+
See LICENSE for details.
|
76
|
+
Copyright (c) 2009 Martin Gamsjaeger (snusnu).
|
77
|
+
Copyright (c) 2010 Montana State University, Bozeman (The Yogo Data Management Team).
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
|
6
|
+
gem 'jeweler', '>= 1.4'
|
7
|
+
require 'jeweler'
|
8
|
+
|
9
|
+
require File.expand_path('../lib/dm-reflection/version', __FILE__)
|
10
|
+
|
11
|
+
Jeweler::Tasks.new do |gem|
|
12
|
+
|
13
|
+
gem.version = DataMapper::Reflection::VERSION
|
14
|
+
|
15
|
+
gem.name = "dm-reflection"
|
16
|
+
gem.summary = %Q{Generates datamapper models from existing database schemas}
|
17
|
+
gem.description = %Q{Generates datamapper models from existing database schemas and export them to files}
|
18
|
+
gem.email = "irjudson@gmail.com"
|
19
|
+
gem.homepage = "http://github.com/irjudson/dm-reflection"
|
20
|
+
gem.authors = ["Martin Gamsjaeger (snusnu), Yogo Team"]
|
21
|
+
|
22
|
+
gem.add_dependency 'dm-core', '~> 0.10.2'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'rspec', '~> 1.3'
|
25
|
+
gem.add_development_dependency 'yard', '~> 0.5'
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
Jeweler::GemcutterTasks.new
|
30
|
+
|
31
|
+
FileList['tasks/**/*.rake'].each { |task| import task }
|
32
|
+
|
33
|
+
rescue LoadError
|
34
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
35
|
+
end
|
36
|
+
|
37
|
+
task :spec => :check_dependencies
|
38
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.2
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dm-reflection}
|
8
|
+
s.version = "0.0.5"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Martin Gamsjaeger (snusnu), Yogo Team"]
|
12
|
+
s.date = %q{2010-05-03}
|
13
|
+
s.description = %q{Generates datamapper models from existing database schemas and export them to files}
|
14
|
+
s.email = %q{irjudson@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"dm-reflection.gemspec",
|
27
|
+
"lib/dm-reflection.rb",
|
28
|
+
"lib/dm-reflection/adapters/mysql.rb",
|
29
|
+
"lib/dm-reflection/adapters/persevere.rb",
|
30
|
+
"lib/dm-reflection/adapters/postgres.rb",
|
31
|
+
"lib/dm-reflection/adapters/sqlite3.rb",
|
32
|
+
"lib/dm-reflection/builders/source_builder.rb",
|
33
|
+
"lib/dm-reflection/reflection.rb",
|
34
|
+
"lib/dm-reflection/version.rb",
|
35
|
+
"spec/persevere_reflection_spec.rb",
|
36
|
+
"spec/rcov.opts",
|
37
|
+
"spec/reflection_spec.rb",
|
38
|
+
"spec/source_builder_spec.rb",
|
39
|
+
"spec/spec.opts",
|
40
|
+
"spec/spec_helper.rb",
|
41
|
+
"tasks/changelog.rake",
|
42
|
+
"tasks/ci.rake",
|
43
|
+
"tasks/metrics.rake",
|
44
|
+
"tasks/spec.rake",
|
45
|
+
"tasks/yard.rake",
|
46
|
+
"tasks/yardstick.rake"
|
47
|
+
]
|
48
|
+
s.homepage = %q{http://github.com/irjudson/dm-reflection}
|
49
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.rubygems_version = %q{1.3.6}
|
52
|
+
s.summary = %q{Generates datamapper models from existing database schemas}
|
53
|
+
s.test_files = [
|
54
|
+
"spec/persevere_reflection_spec.rb",
|
55
|
+
"spec/reflection_spec.rb",
|
56
|
+
"spec/source_builder_spec.rb",
|
57
|
+
"spec/spec_helper.rb"
|
58
|
+
]
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_runtime_dependency(%q<dm-core>, ["~> 0.10.2"])
|
66
|
+
s.add_development_dependency(%q<rspec>, ["~> 1.3"])
|
67
|
+
s.add_development_dependency(%q<yard>, ["~> 0.5"])
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<dm-core>, ["~> 0.10.2"])
|
70
|
+
s.add_dependency(%q<rspec>, ["~> 1.3"])
|
71
|
+
s.add_dependency(%q<yard>, ["~> 0.5"])
|
72
|
+
end
|
73
|
+
else
|
74
|
+
s.add_dependency(%q<dm-core>, ["~> 0.10.2"])
|
75
|
+
s.add_dependency(%q<rspec>, ["~> 1.3"])
|
76
|
+
s.add_dependency(%q<yard>, ["~> 0.5"])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Reflection
|
3
|
+
module MysqlAdapter
|
4
|
+
|
5
|
+
##
|
6
|
+
# Convert the database type into a DataMapper type
|
7
|
+
#
|
8
|
+
# @todo This should be verified to identify all mysql primitive types
|
9
|
+
# and that they map to the correct DataMapper/Ruby types.
|
10
|
+
#
|
11
|
+
# @param [String] db_type type specified by the database
|
12
|
+
# @return [Type] a DataMapper or Ruby type object.
|
13
|
+
#
|
14
|
+
def get_type(db_type)
|
15
|
+
# TODO: return a Hash with the :type, :min, :max and other
|
16
|
+
# options rather than just the type
|
17
|
+
|
18
|
+
db_type.match(/\A(\w+)/)
|
19
|
+
{
|
20
|
+
'tinyint' => Integer ,
|
21
|
+
'smallint' => Integer ,
|
22
|
+
'mediumint' => Integer ,
|
23
|
+
'int' => Integer ,
|
24
|
+
'bigint' => Integer ,
|
25
|
+
'integer' => Integer ,
|
26
|
+
'varchar' => String ,
|
27
|
+
'char' => String ,
|
28
|
+
'enum' => String ,
|
29
|
+
'decimal' => BigDecimal ,
|
30
|
+
'double' => Float ,
|
31
|
+
'float' => Float ,
|
32
|
+
'datetime' => DateTime ,
|
33
|
+
'timestamp' => DateTime ,
|
34
|
+
'date' => Date ,
|
35
|
+
'boolean' => Types::Boolean,
|
36
|
+
'tinyblob' => Types::Text,
|
37
|
+
'blob' => Types::Text,
|
38
|
+
'mediumblob' => Types::Text,
|
39
|
+
'longblob' => Types::Text,
|
40
|
+
'tinytext' => Types::Text,
|
41
|
+
'text' => Types::Text,
|
42
|
+
'mediumtext' => Types::Text,
|
43
|
+
'longtext' => Types::Text,
|
44
|
+
}[$1] || raise("unknown type: #{db_type}")
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Get the list of table names
|
49
|
+
#
|
50
|
+
# @return [String Array] the names of the tables in the database.
|
51
|
+
#
|
52
|
+
def get_storage_names
|
53
|
+
# This gets all the non view tables, but has to strip column 0 out of the two column response.
|
54
|
+
select("SHOW FULL TABLES FROM #{options[:path][1..-1]} WHERE Table_type = 'BASE TABLE'").map { |item| item.first }
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Get the column specifications for a specific table
|
59
|
+
#
|
60
|
+
# @todo Consider returning actual DataMapper::Properties from this.
|
61
|
+
# It would probably require passing in a Model Object.
|
62
|
+
#
|
63
|
+
# @param [String] table the name of the table to get column specifications for
|
64
|
+
# @return [Hash] the column specs are returned in a hash keyed by `:name`, `:field`, `:type`, `:required`, `:default`, `:key`
|
65
|
+
#
|
66
|
+
def get_properties(table)
|
67
|
+
# TODO: use SHOW INDEXES to find out unique and non-unique indexes
|
68
|
+
|
69
|
+
select("SHOW COLUMNS FROM #{table} IN #{options[:path][1..-1]};").map do |column|
|
70
|
+
type = get_type(column.type)
|
71
|
+
auto_increment = column.extra == 'auto_increment'
|
72
|
+
|
73
|
+
if type == Integer && auto_increment
|
74
|
+
type = DataMapper::Types::Serial
|
75
|
+
end
|
76
|
+
|
77
|
+
attribute = {
|
78
|
+
:name => column.field.downcase,
|
79
|
+
:type => type,
|
80
|
+
:required => column.null == 'NO',
|
81
|
+
:default => column.default,
|
82
|
+
:key => column.key == 'PRI',
|
83
|
+
}
|
84
|
+
|
85
|
+
# TODO: use the naming convention to compare the name vs the column name
|
86
|
+
unless attribute[:name] == column.field
|
87
|
+
attribute[:field] = column.field
|
88
|
+
end
|
89
|
+
|
90
|
+
attribute
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end # module MysqlAdapter
|
94
|
+
end # module Reflection
|
95
|
+
end # module DataMapper
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Reflection
|
3
|
+
module PersevereAdapter
|
4
|
+
extend Chainable
|
5
|
+
|
6
|
+
##
|
7
|
+
# Convert the JSON Schema type into a DataMapper type
|
8
|
+
#
|
9
|
+
# @todo This should be verified to identify all mysql primitive types
|
10
|
+
# and that they map to the correct DataMapper/Ruby types.
|
11
|
+
#
|
12
|
+
# @param [String] db_type type specified by the database
|
13
|
+
# @param [String] optional format format specification for string attributes
|
14
|
+
# @return [Type] a DataMapper or Ruby type object.
|
15
|
+
#
|
16
|
+
chainable do
|
17
|
+
def get_type(db_type)
|
18
|
+
type = db_type['type']
|
19
|
+
format = db_type['format']
|
20
|
+
|
21
|
+
case type
|
22
|
+
when Hash then DataMapper::Types::JsonReference
|
23
|
+
when 'array' then DataMapper::Types::JsonReferenceCollection
|
24
|
+
when 'serial' then DataMapper::Types::Serial
|
25
|
+
when 'integer' then Integer
|
26
|
+
# when 'number' then BigDecimal
|
27
|
+
when 'number' then Float
|
28
|
+
when 'boolean' then DataMapper::Types::Boolean
|
29
|
+
when 'string' then
|
30
|
+
case format
|
31
|
+
when nil then DataMapper::Types::Text
|
32
|
+
when 'date-time' then DateTime
|
33
|
+
when 'date' then Date
|
34
|
+
when 'time' then Time
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
##
|
40
|
+
# Get the list of schema names
|
41
|
+
#
|
42
|
+
# @return [String Array] the names of the schemas in the server.
|
43
|
+
#
|
44
|
+
def get_storage_names
|
45
|
+
@schemas = self.get_schema
|
46
|
+
@schemas.map { |schema| schema['id'].gsub('/', '__') }
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Get the attribute specifications for a specific schema
|
51
|
+
#
|
52
|
+
# @todo Consider returning actual DataMapper::Properties from this.
|
53
|
+
# It would probably require passing in a Model Object.
|
54
|
+
#
|
55
|
+
# @param [String] table the name of the schema to get attribute specifications for
|
56
|
+
# @return [Array] of hashes the column specs are returned in a hash keyed by `:name`, `:field`, `:type`, `:required`, `:default`, `:key`
|
57
|
+
#
|
58
|
+
chainable do
|
59
|
+
def get_properties(table)
|
60
|
+
results = Array.new
|
61
|
+
schema = self.get_schema(table.gsub('__', '/'))[0]
|
62
|
+
schema['properties'].each_pair do |key, value|
|
63
|
+
type = get_type(value)
|
64
|
+
property = {:name => key, :type => type }
|
65
|
+
property.merge!({ :required => !value.delete('optional'),
|
66
|
+
:key => value.has_key?('index') && value.delete('index') }) unless property[:type] == DataMapper::Types::Serial
|
67
|
+
|
68
|
+
if type.kind_of?(DataMapper::Types::JsonReference)
|
69
|
+
property.merge!( {:reference => derive_relationship_model(value[:type]["$ref"])} )
|
70
|
+
end
|
71
|
+
|
72
|
+
if type.kind_of?(DataMapper::Types::JsonReferenceCollection)
|
73
|
+
property.merge!( {:reference => derive_relationship_model(value[:items]["$ref"])} )
|
74
|
+
end
|
75
|
+
|
76
|
+
value.delete('type')
|
77
|
+
value.delete('format')
|
78
|
+
value.delete('unique')
|
79
|
+
value.delete('index')
|
80
|
+
value.delete('items')
|
81
|
+
value.keys.each { |key| value[key.to_sym] = value[key]; value.delete(key) }
|
82
|
+
property.merge!(value)
|
83
|
+
results << property
|
84
|
+
end
|
85
|
+
return results
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Turns 'class_path/class' into 'ClassPath::Class
|
92
|
+
def derive_relationship_model(input)
|
93
|
+
input.match(/(Class)?\/([a-z\-\/\_]+)$/)[-1].split('/').map{|i| ExtLib::Inflection.classify(i) }.join("::")
|
94
|
+
end
|
95
|
+
|
96
|
+
end # module PersevereAdapter
|
97
|
+
end # module Reflection
|
98
|
+
end # module DataMapper
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Reflection
|
3
|
+
##
|
4
|
+
# @todo The postgres adapter extensions have not been tested yet.
|
5
|
+
#
|
6
|
+
module PostgresAdapter
|
7
|
+
|
8
|
+
##
|
9
|
+
# Convert the database type into a DataMapper type
|
10
|
+
#
|
11
|
+
# @todo This should be verified to identify all mysql primitive types
|
12
|
+
# and that they map to the correct DataMapper/Ruby types.
|
13
|
+
#
|
14
|
+
# @param [String] db_type type specified by the database
|
15
|
+
# @return [Type] a DataMapper or Ruby type object.
|
16
|
+
#
|
17
|
+
def get_type(db_type)
|
18
|
+
{
|
19
|
+
'integer' => Integer ,
|
20
|
+
'varchar' => String ,
|
21
|
+
'character varying' => String ,
|
22
|
+
'numeric' => BigDecimal ,
|
23
|
+
'double precision' => Float ,
|
24
|
+
'datetime' => DateTime ,
|
25
|
+
'date' => Date ,
|
26
|
+
'boolean' => Types::Boolean ,
|
27
|
+
'text' => Types::Text ,
|
28
|
+
'timestamp without time zone' => DateTime ,
|
29
|
+
}[db_type] || raise("unknown db type: #{db_type}")
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Get the list of table names
|
34
|
+
#
|
35
|
+
# @return [String Array] the names of the tables in the database.
|
36
|
+
#
|
37
|
+
def get_storage_names
|
38
|
+
select(<<-SQL.compress_lines)
|
39
|
+
SELECT "relname"
|
40
|
+
FROM "pg_stat_user_tables"
|
41
|
+
WHERE "schemaname" = 'public'
|
42
|
+
SQL
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returna list of columns in the primary key
|
46
|
+
#
|
47
|
+
# @param [String] the table name
|
48
|
+
#
|
49
|
+
# @return [Array<String>] the names of the columns in the primary key
|
50
|
+
#
|
51
|
+
def get_primary_keys(table)
|
52
|
+
select(<<-SQL.compress_lines, table).to_set
|
53
|
+
SELECT "key_column_usage"."column_name"
|
54
|
+
FROM "information_schema"."table_constraints"
|
55
|
+
INNER JOIN "information_schema"."key_column_usage" USING("constraint_schema", "constraint_name")
|
56
|
+
WHERE "table_constraints"."constraint_type" = 'PRIMARY KEY'
|
57
|
+
AND "table_constraints"."table_name" = ?
|
58
|
+
SQL
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Get the column specifications for a specific table
|
63
|
+
#
|
64
|
+
# @todo Consider returning actual DataMapper::Properties from this.
|
65
|
+
# It would probably require passing in a Model Object.
|
66
|
+
#
|
67
|
+
# @param [String] table the name of the table to get column specifications for
|
68
|
+
# @return [Hash] the column specs are returned in a hash keyed by `:name`, `:field`, `:type`, `:required`, `:default`, `:key`
|
69
|
+
#
|
70
|
+
def get_properties(table)
|
71
|
+
columns = select(<<-SQL.compress_lines, schema_name, table)
|
72
|
+
SELECT "column_name"
|
73
|
+
, "data_type"
|
74
|
+
, "column_default"
|
75
|
+
, "is_nullable"
|
76
|
+
, "character_maximum_length"
|
77
|
+
, "numeric_precision"
|
78
|
+
FROM "information_schema"."columns"
|
79
|
+
WHERE "table_schema" = ?
|
80
|
+
AND "table_name" = ?
|
81
|
+
ORDER BY "ordinal_position"
|
82
|
+
SQL
|
83
|
+
|
84
|
+
primary_keys = get_primary_keys(table)
|
85
|
+
|
86
|
+
columns.map do |column|
|
87
|
+
type = get_type(column.data_type)
|
88
|
+
default = column.column_default
|
89
|
+
required = column.is_nullable == 'NO'
|
90
|
+
key = primary_keys.include?(column.column_name)
|
91
|
+
length = column.character_maximum_length
|
92
|
+
|
93
|
+
if type == Integer && default
|
94
|
+
if key
|
95
|
+
type = DataMapper::Types::Serial if default[/\Anextval/]
|
96
|
+
default = nil
|
97
|
+
else
|
98
|
+
default = default.to_i
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if length
|
103
|
+
length = (required ? 1 : 0)..length
|
104
|
+
end
|
105
|
+
|
106
|
+
attribute = {
|
107
|
+
:name => column.column_name.downcase,
|
108
|
+
:type => type,
|
109
|
+
:required => required,
|
110
|
+
:default => default,
|
111
|
+
:key => key,
|
112
|
+
:length => length,
|
113
|
+
}
|
114
|
+
|
115
|
+
# TODO: use the naming convention to compare the name vs the column name
|
116
|
+
unless attribute[:name] == column.column_name
|
117
|
+
attribute[:field] = column.column_name
|
118
|
+
end
|
119
|
+
|
120
|
+
attribute
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end # module PostgresAdapter
|
125
|
+
end # module Reflection
|
126
|
+
end # module DataMapper
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Reflection
|
3
|
+
module Sqlite3Adapter
|
4
|
+
|
5
|
+
##
|
6
|
+
# Convert the database type into a DataMapper type
|
7
|
+
#
|
8
|
+
# @todo This should be verified to identify all sqlite3 primitive types
|
9
|
+
# and that they map to the correct DataMapper/Ruby types.
|
10
|
+
#
|
11
|
+
# @param [String] db_type type specified by the database
|
12
|
+
# @return [Type] a DataMapper or Ruby type object.
|
13
|
+
#
|
14
|
+
def get_type(db_type)
|
15
|
+
db_type.match(/\A(\w+)/)
|
16
|
+
{
|
17
|
+
'INTEGER' => Integer ,
|
18
|
+
'VARCHAR' => String ,
|
19
|
+
'DECIMAL' => BigDecimal ,
|
20
|
+
'FLOAT' => Float ,
|
21
|
+
'TIMESTAMP' => DateTime ,
|
22
|
+
'DATE' => Date ,
|
23
|
+
'BOOLEAN' => Types::Boolean,
|
24
|
+
'TEXT' => Types::Text
|
25
|
+
}[$1] || raise("unknown db type: #{db_type}")
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Get the list of table names
|
30
|
+
#
|
31
|
+
# @return [String Array] the names of the tables in the database.
|
32
|
+
#
|
33
|
+
def get_storage_names
|
34
|
+
select(<<-SQL.compress_lines)
|
35
|
+
SELECT name
|
36
|
+
FROM (SELECT * FROM sqlite_master UNION SELECT * FROM sqlite_temp_master)
|
37
|
+
WHERE type IN('table', 'view')
|
38
|
+
AND name NOT LIKE 'sqlite_%'
|
39
|
+
ORDER BY name
|
40
|
+
SQL
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Get the column specifications for a specific table
|
45
|
+
#
|
46
|
+
# @todo Consider returning actual DataMapper::Properties from this.
|
47
|
+
# It would probably require passing in a Model Object.
|
48
|
+
#
|
49
|
+
# @param [String] table the name of the table to get column specifications for
|
50
|
+
# @return [Hash] the column specs are returned in a hash keyed by `:name`, `:field`, `:type`, `:required`, `:default`, `:key`
|
51
|
+
#
|
52
|
+
def get_properties(table)
|
53
|
+
# TODO: consider using "SELECT sql FROM sqlite_master WHERE tbl_name = ?"
|
54
|
+
# and parsing the create table statement since it will provide
|
55
|
+
# more information like if a column is auto-incrementing, and what
|
56
|
+
# indexes are used.
|
57
|
+
|
58
|
+
select('PRAGMA table_info(%s)' % table).map do |column|
|
59
|
+
type = get_type(column.type)
|
60
|
+
default = column.dflt_value
|
61
|
+
|
62
|
+
if type == Integer && default
|
63
|
+
default = default.to_i
|
64
|
+
end
|
65
|
+
|
66
|
+
attribute = {
|
67
|
+
:name => column.name.downcase,
|
68
|
+
:type => type,
|
69
|
+
:required => column.notnull == 1,
|
70
|
+
:default => default,
|
71
|
+
:key => column.pk == 1,
|
72
|
+
}
|
73
|
+
|
74
|
+
# TODO: use the naming convention to compare the name vs the column name
|
75
|
+
unless attribute[:name] == column.name
|
76
|
+
attribute[:field] = column.name
|
77
|
+
end
|
78
|
+
|
79
|
+
attribute
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end # module Sqlite3Adapter
|
84
|
+
end # module Reflection
|
85
|
+
end # module DataMapper
|