database-model-generator 0.6.0 → 0.7.0

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.
@@ -0,0 +1,278 @@
1
+ # PostgreSQL Support
2
+
3
+ This document describes the PostgreSQL implementation for the database-model-generator library.
4
+
5
+ ## Overview
6
+
7
+ PostgreSQL support has been added to the database-model-generator library, allowing you to generate ActiveRecord models from existing PostgreSQL tables and views. The implementation follows the same architecture as Oracle and SQL Server support.
8
+
9
+ ## Features
10
+
11
+ The PostgreSQL generator includes:
12
+
13
+ - **Full table introspection** via PostgreSQL's `information_schema`
14
+ - **Primary key detection** using `pg_index` and `pg_attribute`
15
+ - **Foreign key detection** with automatic relationship inference
16
+ - **Constraint detection** including CHECK, UNIQUE, and NOT NULL constraints
17
+ - **Enum detection** from CHECK constraints with IN clauses
18
+ - **Polymorphic association detection** (e.g., commentable_id + commentable_type)
19
+ - **Index recommendations** for foreign keys, dates, text search, and composite indexes
20
+ - **Full-text search support** using PostgreSQL's GIN indexes and tsvector
21
+ - **Dependency tracking** for views and related objects
22
+
23
+ ## Installation
24
+
25
+ Add the `pg` gem to your Gemfile or install it directly:
26
+
27
+ ```bash
28
+ gem install pg
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### With the Command Line Tool
34
+
35
+ ```bash
36
+ # Using explicit database type
37
+ dmg -T postgresql -h localhost -d your_database -t users -u postgres [-p password]
38
+
39
+ # Using auto-detection (if connection parameters indicate PostgreSQL)
40
+ dmg -h localhost -d your_database -t users -u postgres [-p password]
41
+
42
+ # Password is optional if pg_hba.conf is configured for trust authentication
43
+ dmg -T postgresql -h localhost -d your_database -t users -u postgres
44
+ ```
45
+
46
+ ### Programmatically
47
+
48
+ ```ruby
49
+ require 'pg'
50
+ require 'database_model_generator'
51
+
52
+ # Connect to PostgreSQL
53
+ # Note: password is optional if pg_hba.conf allows trust authentication
54
+ conn = PG.connect(
55
+ host: 'localhost',
56
+ port: 5432,
57
+ dbname: 'your_database',
58
+ user: 'postgres'
59
+ # password: 'your_password' # Only needed if authentication is required
60
+ )
61
+
62
+ # Create a generator instance
63
+ generator = DatabaseModel::Generator.new(conn)
64
+
65
+ # Generate model information
66
+ generator.generate('users')
67
+
68
+ # Access model information
69
+ puts "Model name: #{generator.model}"
70
+ puts "Table name: #{generator.table}"
71
+ puts "Columns: #{generator.column_names.join(', ')}"
72
+ puts "Primary keys: #{generator.primary_keys.join(', ')}"
73
+ puts "Foreign keys: #{generator.foreign_keys.join(', ')}"
74
+
75
+ # Get enum columns
76
+ if generator.has_enum_columns?
77
+ puts "\nEnum columns detected:"
78
+ generator.enum_columns.each do |enum_col|
79
+ puts " #{enum_col[:name]}: #{enum_col[:values].join(', ')}"
80
+ end
81
+ end
82
+
83
+ # Get polymorphic associations
84
+ if generator.has_polymorphic_associations?
85
+ puts "\nPolymorphic associations:"
86
+ generator.polymorphic_associations.each do |assoc|
87
+ puts " #{assoc[:name]} (#{assoc[:foreign_key]}, #{assoc[:foreign_type]})"
88
+ end
89
+ end
90
+
91
+ # Get index recommendations
92
+ recommendations = generator.index_recommendations
93
+ puts "\nIndex recommendations:"
94
+ recommendations[:foreign_keys].each do |rec|
95
+ puts " #{rec[:sql]}"
96
+ end
97
+
98
+ # Close connection
99
+ generator.disconnect
100
+ ```
101
+
102
+ ## PostgreSQL-Specific Features
103
+
104
+ ### Full-Text Search
105
+
106
+ PostgreSQL's powerful full-text search is supported through GIN indexes:
107
+
108
+ ```ruby
109
+ generator.generate('posts')
110
+ recommendations = generator.index_recommendations
111
+
112
+ # Full-text recommendations use to_tsvector
113
+ recommendations[:full_text].each do |rec|
114
+ puts rec[:sql]
115
+ # CREATE INDEX idx_posts_content_text ON posts
116
+ # USING GIN (to_tsvector('english', content))
117
+ end
118
+ ```
119
+
120
+ ### Enum Detection from CHECK Constraints
121
+
122
+ PostgreSQL CHECK constraints are parsed to detect enum columns:
123
+
124
+ ```sql
125
+ CREATE TABLE users (
126
+ status VARCHAR(20) DEFAULT 'active',
127
+ CONSTRAINT check_status CHECK (status IN ('active', 'inactive', 'suspended'))
128
+ );
129
+ ```
130
+
131
+ ```ruby
132
+ generator.generate('users')
133
+ generator.enum_columns
134
+ # => [{name: 'status', values: ['active', 'inactive', 'suspended'], ...}]
135
+ ```
136
+
137
+ ### Data Type Support
138
+
139
+ The PostgreSQL generator handles all standard PostgreSQL data types:
140
+
141
+ - **Character types**: CHAR, VARCHAR, TEXT, CHARACTER VARYING
142
+ - **Numeric types**: INTEGER, BIGINT, DECIMAL, NUMERIC, REAL, DOUBLE PRECISION
143
+ - **Date/Time types**: DATE, TIME, TIMESTAMP, TIMESTAMPTZ, INTERVAL
144
+ - **Boolean type**: BOOLEAN
145
+ - **Binary types**: BYTEA
146
+ - **JSON types**: JSON, JSONB
147
+ - **Arrays and custom types**: Detected via `udt_name` column
148
+
149
+ ## Testing
150
+
151
+ ### Using Docker
152
+
153
+ The easiest way to test PostgreSQL support is using the provided Docker setup:
154
+
155
+ ```bash
156
+ cd docker/postgresql
157
+
158
+ # Start PostgreSQL
159
+ docker-compose up -d postgres-db
160
+
161
+ # Wait for it to be healthy
162
+ docker-compose ps
163
+
164
+ # Run tests
165
+ docker-compose run --rm dmg_test
166
+ ```
167
+
168
+ ### Manual Testing
169
+
170
+ If you have PostgreSQL installed locally:
171
+
172
+ ```bash
173
+ # Create test database
174
+ createdb test_db
175
+
176
+ # Run initialization script
177
+ psql -d test_db -f docker/postgresql/init-db.sql
178
+
179
+ # Run tests
180
+ bundle exec rspec spec/postgresql_model_generator_spec.rb
181
+ ```
182
+
183
+ ## Database Schema Example
184
+
185
+ The test database includes:
186
+
187
+ - **users** - with status and role enum columns
188
+ - **posts** - with foreign key to users, status and priority enums
189
+ - **comments** - with polymorphic association (commentable)
190
+ - **tags** - simple lookup table
191
+ - **post_tags** - many-to-many join table with composite primary key
192
+
193
+ ## Architecture
194
+
195
+ The PostgreSQL generator is implemented in `lib/postgresql/model/generator.rb` and extends the `DatabaseModel::Generator::Base` class, providing PostgreSQL-specific implementations for:
196
+
197
+ - `validate_connection` - ensures connection is a `PG::Connection`
198
+ - `normalize_table_name` - converts to lowercase (PostgreSQL convention)
199
+ - `check_table_exists` - queries `information_schema.tables`
200
+ - `get_column_info` - retrieves column metadata
201
+ - `get_primary_keys` - queries `pg_index` system catalog
202
+ - `get_foreign_keys` - queries `information_schema.table_constraints`
203
+ - `get_constraints` - retrieves all constraint information
204
+ - `get_dependencies` - finds dependent views and objects
205
+ - Type checking methods for PostgreSQL data types
206
+
207
+ ## Comparison with Other Databases
208
+
209
+ | Feature | Oracle | SQL Server | PostgreSQL |
210
+ |---------|--------|------------|------------|
211
+ | Connection | OCI8 | TinyTds::Client | PG::Connection |
212
+ | Schema queries | USER_* views | INFORMATION_SCHEMA | INFORMATION_SCHEMA + pg_* |
213
+ | Table name case | UPPERCASE | Case-sensitive | lowercase |
214
+ | Full-text search | Oracle Text | FULLTEXT INDEX | GIN + tsvector |
215
+ | Enum detection | ✓ | ✓ | ✓ |
216
+ | Polymorphic | ✓ | ✓ | ✓ |
217
+ | Composite PKs | ✓ | ✓ | ✓ |
218
+
219
+ ## Troubleshooting
220
+
221
+ ### Connection Issues
222
+
223
+ If you get connection errors:
224
+
225
+ ```ruby
226
+ PG::ConnectionBad: could not connect to server
227
+ ```
228
+
229
+ Check:
230
+ 1. PostgreSQL is running: `pg_isready`
231
+ 2. Connection parameters are correct
232
+ 3. User has proper permissions
233
+ 4. `pg_hba.conf` allows the connection
234
+
235
+ ### Missing Tables
236
+
237
+ If tables aren't found:
238
+
239
+ ```ruby
240
+ generator.table_exists? # => false
241
+ ```
242
+
243
+ Check:
244
+ 1. Table is in the `public` schema (or adjust schema in queries)
245
+ 2. User has SELECT permission on the table
246
+ 3. Table name is correct (case-sensitive in PostgreSQL when quoted)
247
+
248
+ ### Gem Installation Issues
249
+
250
+ On macOS with Homebrew PostgreSQL:
251
+
252
+ ```bash
253
+ gem install pg -- --with-pg-config=/usr/local/bin/pg_config
254
+ ```
255
+
256
+ Or with a specific PostgreSQL version:
257
+
258
+ ```bash
259
+ gem install pg -- --with-pg-config=/usr/local/opt/postgresql@14/bin/pg_config
260
+ ```
261
+
262
+ ## Future Enhancements
263
+
264
+ Possible future improvements:
265
+
266
+ - Support for PostgreSQL-specific features (arrays, JSON columns)
267
+ - Custom type (enum, composite) detection
268
+ - Partitioned table support
269
+ - Foreign data wrapper detection
270
+ - Materialized view support
271
+ - PostgreSQL extension detection (PostGIS, etc.)
272
+
273
+ ## References
274
+
275
+ - [PostgreSQL Documentation](https://www.postgresql.org/docs/)
276
+ - [pg gem documentation](https://github.com/ged/ruby-pg)
277
+ - [PostgreSQL INFORMATION_SCHEMA](https://www.postgresql.org/docs/current/information-schema.html)
278
+ - [PostgreSQL System Catalogs](https://www.postgresql.org/docs/current/catalogs.html)
@@ -0,0 +1,116 @@
1
+ # PostgreSQL Docker Setup for Database Model Generator
2
+
3
+ This directory contains Docker setup files for testing the PostgreSQL model generator.
4
+
5
+ ## Prerequisites
6
+
7
+ - Docker
8
+ - Docker Compose
9
+
10
+ ## Quick Start
11
+
12
+ 1. Start the PostgreSQL container:
13
+
14
+ ```bash
15
+ cd docker/postgresql
16
+ docker-compose up -d postgres-db
17
+ ```
18
+
19
+ 2. Wait for PostgreSQL to be ready (check with):
20
+
21
+ ```bash
22
+ docker-compose logs -f postgres-db
23
+ ```
24
+
25
+ 3. Run the tests:
26
+
27
+ ```bash
28
+ docker-compose run --rm dmg_test
29
+ ```
30
+
31
+ Or run tests directly:
32
+
33
+ ```bash
34
+ docker-compose run --rm dmg_test bundle exec rspec spec/postgresql_model_generator_spec.rb
35
+ ```
36
+
37
+ ## Environment Variables
38
+
39
+ The following environment variables are available:
40
+
41
+ - `POSTGRES_HOST`: PostgreSQL host (default: postgres-db)
42
+ - `POSTGRES_PORT`: PostgreSQL port (default: 5432)
43
+ - `POSTGRES_USER`: PostgreSQL username (default: postgres)
44
+ - `POSTGRES_PASSWORD`: PostgreSQL password (optional, omit if using trust authentication)
45
+ - `POSTGRES_DATABASE`: PostgreSQL database name (default: test_db)
46
+
47
+ ## Sample Database
48
+
49
+ The `init-db.sql` file creates a sample database schema with:
50
+
51
+ - **users** table - with status and role enum columns
52
+ - **posts** table - with foreign key to users, status and priority enums
53
+ - **comments** table - with polymorphic association (commentable)
54
+ - **tags** table - simple lookup table
55
+ - **post_tags** table - many-to-many join table
56
+
57
+ This schema is designed to test various features of the model generator including:
58
+ - Foreign key detection
59
+ - Enum column detection
60
+ - Polymorphic associations
61
+ - Primary keys
62
+ - Constraints
63
+ - Indexes
64
+ - Full-text search recommendations
65
+
66
+ ## Manual Testing
67
+
68
+ To connect to PostgreSQL manually:
69
+
70
+ ```bash
71
+ docker-compose exec postgres-db psql -U postgres -d test_db
72
+ ```
73
+
74
+ ## Stopping and Cleaning Up
75
+
76
+ Stop the containers:
77
+
78
+ ```bash
79
+ docker-compose down
80
+ ```
81
+
82
+ Stop and remove volumes (warning: this deletes all data):
83
+
84
+ ```bash
85
+ docker-compose down -v
86
+ ```
87
+
88
+ ## Troubleshooting
89
+
90
+ ### PostgreSQL won't start
91
+
92
+ Check the logs:
93
+
94
+ ```bash
95
+ docker-compose logs postgres-db
96
+ ```
97
+
98
+ ### Connection refused
99
+
100
+ Make sure the healthcheck passes:
101
+
102
+ ```bash
103
+ docker-compose ps
104
+ ```
105
+
106
+ The postgres-db service should show as "healthy".
107
+
108
+ ### Tests fail
109
+
110
+ Ensure the database is initialized properly:
111
+
112
+ ```bash
113
+ docker-compose exec postgres-db psql -U postgres -d test_db -c "\dt"
114
+ ```
115
+
116
+ You should see the tables listed.
@@ -0,0 +1,51 @@
1
+ services:
2
+ # PostgreSQL database for testing
3
+ postgres-db:
4
+ image: postgres:16-alpine
5
+ container_name: dmg_postgres
6
+ environment:
7
+ - POSTGRES_USER=postgres
8
+ - POSTGRES_PASSWORD=postgres
9
+ - POSTGRES_DB=test_db
10
+ ports:
11
+ - "5432:5432"
12
+ volumes:
13
+ - postgres_data:/var/lib/postgresql/data
14
+ - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
15
+ healthcheck:
16
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
17
+ interval: 10s
18
+ timeout: 5s
19
+ retries: 5
20
+ start_period: 30s
21
+ networks:
22
+ - dmg_network
23
+
24
+ # Optional: Add a container to test the generator
25
+ dmg_test:
26
+ build:
27
+ context: ../../
28
+ dockerfile: docker/postgresql/test-Dockerfile
29
+ container_name: dmg_test_runner
30
+ depends_on:
31
+ postgres-db:
32
+ condition: service_healthy
33
+ volumes:
34
+ - ../../:/app
35
+ working_dir: /app
36
+ environment:
37
+ - POSTGRES_HOST=postgres-db
38
+ - POSTGRES_PORT=5432
39
+ - POSTGRES_USER=postgres
40
+ - POSTGRES_PASSWORD=postgres
41
+ - POSTGRES_DATABASE=test_db
42
+ networks:
43
+ - dmg_network
44
+ command: ["./docker/postgresql/test.sh"]
45
+
46
+ volumes:
47
+ postgres_data:
48
+
49
+ networks:
50
+ dmg_network:
51
+ driver: bridge
@@ -0,0 +1,101 @@
1
+ -- PostgreSQL test database initialization script
2
+ -- Create sample tables for testing the model generator
3
+
4
+ -- Create users table
5
+ CREATE TABLE users (
6
+ id SERIAL PRIMARY KEY,
7
+ username VARCHAR(50) NOT NULL UNIQUE,
8
+ email VARCHAR(100) NOT NULL UNIQUE,
9
+ first_name VARCHAR(50),
10
+ last_name VARCHAR(50),
11
+ status VARCHAR(20) DEFAULT 'active',
12
+ role VARCHAR(20) DEFAULT 'user',
13
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
14
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
15
+ CONSTRAINT check_status CHECK (status IN ('active', 'inactive', 'suspended', 'deleted')),
16
+ CONSTRAINT check_role CHECK (role IN ('user', 'admin', 'moderator'))
17
+ );
18
+
19
+ -- Create posts table
20
+ CREATE TABLE posts (
21
+ id SERIAL PRIMARY KEY,
22
+ user_id INTEGER NOT NULL,
23
+ title VARCHAR(200) NOT NULL,
24
+ content TEXT,
25
+ status VARCHAR(20) DEFAULT 'draft',
26
+ priority VARCHAR(10) DEFAULT 'medium',
27
+ published_at TIMESTAMP,
28
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
29
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
30
+ CONSTRAINT fk_posts_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
31
+ CONSTRAINT check_post_status CHECK (status IN ('draft', 'published', 'archived')),
32
+ CONSTRAINT check_priority CHECK (priority IN ('low', 'medium', 'high', 'urgent'))
33
+ );
34
+
35
+ -- Create comments table (with polymorphic association)
36
+ CREATE TABLE comments (
37
+ id SERIAL PRIMARY KEY,
38
+ user_id INTEGER NOT NULL,
39
+ commentable_id INTEGER NOT NULL,
40
+ commentable_type VARCHAR(50) NOT NULL,
41
+ content TEXT NOT NULL,
42
+ status VARCHAR(20) DEFAULT 'active',
43
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
44
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
45
+ CONSTRAINT fk_comments_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
46
+ CONSTRAINT check_comment_status CHECK (status IN ('active', 'hidden', 'deleted'))
47
+ );
48
+
49
+ -- Create tags table
50
+ CREATE TABLE tags (
51
+ id SERIAL PRIMARY KEY,
52
+ name VARCHAR(50) NOT NULL UNIQUE,
53
+ slug VARCHAR(60) NOT NULL UNIQUE,
54
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
55
+ );
56
+
57
+ -- Create post_tags (join table)
58
+ CREATE TABLE post_tags (
59
+ post_id INTEGER NOT NULL,
60
+ tag_id INTEGER NOT NULL,
61
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
62
+ PRIMARY KEY (post_id, tag_id),
63
+ CONSTRAINT fk_post_tags_post FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
64
+ CONSTRAINT fk_post_tags_tag FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
65
+ );
66
+
67
+ -- Create indexes for better query performance
68
+ CREATE INDEX idx_posts_user_id ON posts(user_id);
69
+ CREATE INDEX idx_posts_status ON posts(status);
70
+ CREATE INDEX idx_posts_created_at ON posts(created_at);
71
+ CREATE INDEX idx_comments_user_id ON comments(user_id);
72
+ CREATE INDEX idx_comments_commentable ON comments(commentable_id, commentable_type);
73
+
74
+ -- Create a full-text search index
75
+ CREATE INDEX idx_posts_title_content_text ON posts USING GIN (to_tsvector('english', title || ' ' || COALESCE(content, '')));
76
+
77
+ -- Insert sample data
78
+ INSERT INTO users (username, email, first_name, last_name, status, role) VALUES
79
+ ('john_doe', 'john@example.com', 'John', 'Doe', 'active', 'admin'),
80
+ ('jane_smith', 'jane@example.com', 'Jane', 'Smith', 'active', 'user'),
81
+ ('bob_jones', 'bob@example.com', 'Bob', 'Jones', 'inactive', 'moderator');
82
+
83
+ INSERT INTO posts (user_id, title, content, status, priority) VALUES
84
+ (1, 'First Post', 'This is the content of the first post', 'published', 'high'),
85
+ (1, 'Second Post', 'This is the content of the second post', 'draft', 'medium'),
86
+ (2, 'Jane''s Post', 'Jane writes about something interesting', 'published', 'low');
87
+
88
+ INSERT INTO tags (name, slug) VALUES
89
+ ('Technology', 'technology'),
90
+ ('Ruby', 'ruby'),
91
+ ('PostgreSQL', 'postgresql');
92
+
93
+ INSERT INTO post_tags (post_id, tag_id) VALUES
94
+ (1, 1),
95
+ (1, 2),
96
+ (3, 1),
97
+ (3, 3);
98
+
99
+ INSERT INTO comments (user_id, commentable_id, commentable_type, content) VALUES
100
+ (2, 1, 'Post', 'Great post!'),
101
+ (3, 1, 'Post', 'Thanks for sharing');
@@ -0,0 +1,20 @@
1
+ FROM ruby:3.2-alpine
2
+
3
+ # Install build dependencies
4
+ RUN apk add --no-cache \
5
+ build-base \
6
+ postgresql-dev \
7
+ postgresql-client \
8
+ git
9
+
10
+ # Set working directory
11
+ WORKDIR /app
12
+
13
+ # Copy application files
14
+ COPY . .
15
+
16
+ # Install dependencies
17
+ RUN bundle install
18
+
19
+ # Run tests
20
+ CMD ["bundle", "exec", "rspec", "spec/postgresql_model_generator_spec.rb"]
@@ -0,0 +1,18 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "Waiting for PostgreSQL to be ready..."
5
+ until pg_isready -h $POSTGRES_HOST -p $POSTGRES_PORT -U $POSTGRES_USER; do
6
+ echo "PostgreSQL is unavailable - sleeping"
7
+ sleep 2
8
+ done
9
+
10
+ echo "PostgreSQL is up - running tests"
11
+
12
+ # Install dependencies if needed
13
+ bundle check || bundle install
14
+
15
+ # Run the tests
16
+ bundle exec rspec spec/postgresql_model_generator_spec.rb -fd
17
+
18
+ echo "Tests completed!"
@@ -10,10 +10,16 @@ rescue LoadError
10
10
  # TinyTDS not available - SQL Server support will be disabled
11
11
  end
12
12
 
13
+ begin
14
+ require 'pg'
15
+ rescue LoadError
16
+ # pg not available - PostgreSQL support will be disabled
17
+ end
18
+
13
19
  module DatabaseModel
14
20
  module Generator
15
21
  # The version of the database-model-generator library
16
- VERSION = '0.6.0'
22
+ VERSION = '0.7.0'
17
23
 
18
24
  # Factory method to create the appropriate generator based on connection type
19
25
  def self.new(connection, options = {})
@@ -26,6 +32,9 @@ module DatabaseModel
26
32
  when :sqlserver
27
33
  require_relative 'sqlserver/model/generator'
28
34
  SqlServer::Model::Generator.new(connection)
35
+ when :postgresql
36
+ require_relative 'postgresql/model/generator'
37
+ Postgresql::Model::Generator.new(connection)
29
38
  else
30
39
  raise ArgumentError, "Unsupported database type: #{database_type}"
31
40
  end
@@ -41,6 +50,8 @@ module DatabaseModel
41
50
  :oracle
42
51
  when 'TinyTds::Client'
43
52
  :sqlserver
53
+ when 'PG::Connection'
54
+ :postgresql
44
55
  else
45
56
  raise ArgumentError, "Cannot detect database type from connection: #{connection.class}"
46
57
  end