| 1 | # Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd. |
|---|
| 2 | # This program is distributed under the terms of the |
|---|
| 3 | # GNU General Public License, version 2. |
|---|
| 4 | # |
|---|
| 5 | # $Id$ |
|---|
| 6 | |
|---|
| 7 | package MT::ObjectDriver::DDL::SQLite; |
|---|
| 8 | |
|---|
| 9 | use strict; |
|---|
| 10 | use warnings; |
|---|
| 11 | use base qw( MT::ObjectDriver::DDL ); |
|---|
| 12 | |
|---|
| 13 | sub index_defs { |
|---|
| 14 | my $ddl = shift; |
|---|
| 15 | my ($class) = @_; |
|---|
| 16 | my $driver = $class->driver; |
|---|
| 17 | my $dbh = $driver->r_handle; |
|---|
| 18 | my $field_prefix = $class->datasource; |
|---|
| 19 | my $table_name = $class->table_name; |
|---|
| 20 | my $sth = $dbh->prepare(<<SQL) |
|---|
| 21 | SELECT name, sql |
|---|
| 22 | FROM sqlite_master |
|---|
| 23 | WHERE type = "index" |
|---|
| 24 | AND tbl_name="$table_name" |
|---|
| 25 | SQL |
|---|
| 26 | or return undef; |
|---|
| 27 | $sth->execute or return undef; |
|---|
| 28 | |
|---|
| 29 | my $defs = {}; |
|---|
| 30 | while (my $row = $sth->fetchrow_hashref) { |
|---|
| 31 | my $key = $row->{'name'}; |
|---|
| 32 | next unless $key =~ m/^(mt_)?\Q$field_prefix\E_/; |
|---|
| 33 | next if $key =~ m/(.+autoindex)/; |
|---|
| 34 | my $sql = $row->{'sql'} |
|---|
| 35 | or next; |
|---|
| 36 | |
|---|
| 37 | $key =~ s/^mt_\Q$field_prefix\E_//; |
|---|
| 38 | my $cols = []; |
|---|
| 39 | my $is_unique = 0; |
|---|
| 40 | my $idx_columns; |
|---|
| 41 | if ( $sql =~ m/CREATE( UNIQUE)? INDEX (?:.+?) ON $table_name \((.+?)\)/i ) { |
|---|
| 42 | $is_unique = $1 ? 'unique' eq lc($1) : 0; |
|---|
| 43 | $idx_columns = $2; |
|---|
| 44 | for my $col ( split ',', $idx_columns ) { |
|---|
| 45 | $col =~ s/^\Q$field_prefix\E_//; |
|---|
| 46 | push @$cols, $col; |
|---|
| 47 | } |
|---|
| 48 | } |
|---|
| 49 | unless ( $is_unique ) { |
|---|
| 50 | # Check constraints to identify unique index |
|---|
| 51 | my $sth_tbl = $dbh->prepare(<<TBLSQL); |
|---|
| 52 | SELECT name, sql FROM sqlite_master |
|---|
| 53 | WHERE type = "table" AND name = "$table_name" |
|---|
| 54 | TBLSQL |
|---|
| 55 | $sth_tbl->execute or next; |
|---|
| 56 | my $rows_tbl = $sth_tbl->fetchall_hashref('name'); |
|---|
| 57 | $sth_tbl->finish; |
|---|
| 58 | my $sql_tbl = $rows_tbl->{$table_name}->{'sql'} |
|---|
| 59 | or next; |
|---|
| 60 | my $idx_name = $row->{'name'}; |
|---|
| 61 | if ( $sql_tbl =~ m/CONSTRAINT\s+$idx_name\s+UNIQUE\s+\(\s*$idx_columns\s*\)/im ) { |
|---|
| 62 | $is_unique = 1; |
|---|
| 63 | } |
|---|
| 64 | } |
|---|
| 65 | |
|---|
| 66 | if ( $is_unique ) { |
|---|
| 67 | $defs->{$key} = { 'unique' => 1, 'columns' => $cols }; |
|---|
| 68 | } |
|---|
| 69 | else { |
|---|
| 70 | if ((@$cols == 1) && ($key eq $cols->[0])) { |
|---|
| 71 | $defs->{$key} = 1; |
|---|
| 72 | } else { |
|---|
| 73 | $defs->{$key} = { 'columns' => $cols }; |
|---|
| 74 | } |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | } |
|---|
| 78 | $sth->finish; |
|---|
| 79 | return undef unless %$defs; |
|---|
| 80 | |
|---|
| 81 | return $defs; |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | sub column_defs { |
|---|
| 85 | my $ddl = shift; |
|---|
| 86 | my ($class) = @_; |
|---|
| 87 | |
|---|
| 88 | my $driver = $class->driver; |
|---|
| 89 | my $dbh = $driver->r_handle; |
|---|
| 90 | my $table_name = $class->table_name; |
|---|
| 91 | my $field_prefix = $class->datasource; |
|---|
| 92 | |
|---|
| 93 | return undef unless $dbh; |
|---|
| 94 | |
|---|
| 95 | # Disable RaiseError if set, since the table we're about to describe |
|---|
| 96 | # may not actually exist (in which case, the return value is undef, |
|---|
| 97 | # signalling an nonexistent table to the caller). |
|---|
| 98 | local $dbh->{RaiseError} = 1; |
|---|
| 99 | my $sth = $dbh->prepare('PRAGMA table_info("' . $table_name . '")') |
|---|
| 100 | or return undef; |
|---|
| 101 | $sth->execute or return undef; |
|---|
| 102 | my $defs = {}; |
|---|
| 103 | while (my $row = $sth->fetchrow_hashref) { |
|---|
| 104 | my $colname = lc $row->{name}; |
|---|
| 105 | $colname =~ s/^\Q$field_prefix\E_//i; |
|---|
| 106 | my $coltype = $ddl->db2type($row->{type}); |
|---|
| 107 | if ($row->{type} =~ m/\((\d+)\)/) { |
|---|
| 108 | $defs->{$colname}{size} = $1; |
|---|
| 109 | } |
|---|
| 110 | $defs->{$colname}{type} = $coltype; |
|---|
| 111 | if ($colname =~ m/_id$/) { |
|---|
| 112 | $defs->{$colname}{key} = 1; |
|---|
| 113 | } |
|---|
| 114 | if ( ($coltype eq 'integer') && $row->{pk} ) { |
|---|
| 115 | # with sqlite, integer primary keys auto increment. always. |
|---|
| 116 | $defs->{$colname}{key} = 1; |
|---|
| 117 | $defs->{$colname}{auto} = 1; |
|---|
| 118 | } |
|---|
| 119 | $defs->{$colname}{not_null} = 1 |
|---|
| 120 | if $row->{notnull}; |
|---|
| 121 | $defs->{$colname}{default} = $row->{dflt_value} |
|---|
| 122 | if defined $row->{dflt_value}; |
|---|
| 123 | } |
|---|
| 124 | $sth->finish; |
|---|
| 125 | return undef unless %$defs; |
|---|
| 126 | return $defs; |
|---|
| 127 | } |
|---|
| 128 | |
|---|
| 129 | sub db2type { |
|---|
| 130 | my $ddl = shift; |
|---|
| 131 | my ($db_type) = @_; |
|---|
| 132 | $db_type =~ s/\(\d+\)//g; |
|---|
| 133 | if ($db_type eq 'varchar') { |
|---|
| 134 | $db_type = 'string'; |
|---|
| 135 | } |
|---|
| 136 | return $db_type; |
|---|
| 137 | } |
|---|
| 138 | |
|---|
| 139 | sub type2db { |
|---|
| 140 | my $ddl = shift; |
|---|
| 141 | my ($def) = @_; |
|---|
| 142 | return undef if !defined $def; |
|---|
| 143 | my $type = (ref($def) eq 'HASH') ? $def->{type} : $def; |
|---|
| 144 | $type = $def->{type}; |
|---|
| 145 | if ($type eq 'string') { |
|---|
| 146 | return 'varchar(' . $def->{size} . ')'; |
|---|
| 147 | } elsif ($type eq 'smallint' ) { |
|---|
| 148 | return 'smallint'; |
|---|
| 149 | } elsif ($type eq 'bigint' ) { |
|---|
| 150 | return 'bigint'; |
|---|
| 151 | } elsif ($type eq 'boolean') { |
|---|
| 152 | return 'boolean'; |
|---|
| 153 | } elsif ($type eq 'datetime') { |
|---|
| 154 | return 'datetime'; |
|---|
| 155 | } elsif ($type eq 'timestamp') { |
|---|
| 156 | return 'timestamp'; |
|---|
| 157 | } elsif ($type eq 'integer') { |
|---|
| 158 | return 'integer'; |
|---|
| 159 | } elsif ($type eq 'blob') { |
|---|
| 160 | return 'blob'; |
|---|
| 161 | } elsif ($type eq 'text') { |
|---|
| 162 | return 'text'; |
|---|
| 163 | } elsif ($type eq 'float') { |
|---|
| 164 | return 'float'; |
|---|
| 165 | } |
|---|
| 166 | Carp::croak("undefined type: ". $type); |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | sub can_add_constraint { 0 } |
|---|
| 170 | |
|---|
| 171 | sub unique_constraint_sql { |
|---|
| 172 | my $ddl = shift; |
|---|
| 173 | my ($class) = @_; |
|---|
| 174 | |
|---|
| 175 | my $table_name = $class->table_name; |
|---|
| 176 | my $props = $class->properties; |
|---|
| 177 | my $field_prefix = $class->datasource; |
|---|
| 178 | my $indexes = $props->{indexes}; |
|---|
| 179 | |
|---|
| 180 | my @stmts; |
|---|
| 181 | if ($indexes) { |
|---|
| 182 | # FIXME: Handle possible future primary key tuple case |
|---|
| 183 | my $pk = $props->{primary_key}; |
|---|
| 184 | foreach my $name (keys %$indexes) { |
|---|
| 185 | next if $pk && $name eq $pk; |
|---|
| 186 | if (ref $indexes->{$name} eq 'HASH') { |
|---|
| 187 | my $idx_info = $indexes->{$name}; |
|---|
| 188 | next unless exists($idx_info->{unique}) && $idx_info->{unique}; |
|---|
| 189 | my $column_list = $idx_info->{columns} || [ $name ]; |
|---|
| 190 | my $columns = ''; |
|---|
| 191 | foreach my $col (@$column_list) { |
|---|
| 192 | $columns .= ',' unless $columns eq ''; |
|---|
| 193 | $columns .= $field_prefix . '_' . $col; |
|---|
| 194 | } |
|---|
| 195 | if ($columns) { |
|---|
| 196 | push @stmts, "CONSTRAINT ${table_name}_$name UNIQUE ($columns)"; |
|---|
| 197 | } |
|---|
| 198 | } |
|---|
| 199 | } |
|---|
| 200 | } |
|---|
| 201 | if (@stmts) { |
|---|
| 202 | return ',' . join("\n", @stmts); |
|---|
| 203 | } |
|---|
| 204 | return q(); |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | sub drop_index_sql { |
|---|
| 208 | my $ddl = shift; |
|---|
| 209 | my ($class, $key) = @_; |
|---|
| 210 | my $table_name = $class->table_name; |
|---|
| 211 | |
|---|
| 212 | my $props = $class->properties; |
|---|
| 213 | my $indexes = $props->{indexes}; |
|---|
| 214 | return q() unless exists($indexes->{$key}); |
|---|
| 215 | |
|---|
| 216 | return "DROP INDEX ${table_name}_$key"; |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | 1; |
|---|