AlkantarClanX12
Your IP : 216.73.217.24
<?php
namespace Yoast\WP\Lib\Migrations;
use Exception;
use Yoast\WP\Lib\Model;
/**
* Yoast migrations adapter class.
*/
class Adapter {
/**
* The version of this adapter.
*
* @var string
*/
private $version = '1.0';
/**
* Whether or not a transaction has been started.
*
* @var bool
*/
private $in_transaction = false;
/**
* Returns the current database name.
*
* @return string
*/
public function get_database_name() {
global $wpdb;
return $wpdb->dbname;
}
/**
* Checks support for migrations.
*
* @return bool
*/
public function supports_migrations() {
return true;
}
/**
* Returns all column native types.
*
* @return array
*/
public function native_database_types() {
$types = [
'primary_key' => [
'name' => 'integer',
'limit' => 11,
'null' => false,
],
'string' => [
'name' => 'varchar',
'limit' => 255,
],
'text' => [ 'name' => 'text' ],
'tinytext' => [ 'name' => 'tinytext' ],
'mediumtext' => [ 'name' => 'mediumtext' ],
'integer' => [
'name' => 'int',
'limit' => 11,
],
'tinyinteger' => [ 'name' => 'tinyint' ],
'smallinteger' => [ 'name' => 'smallint' ],
'mediuminteger' => [ 'name' => 'mediumint' ],
'biginteger' => [ 'name' => 'bigint' ],
'float' => [ 'name' => 'float' ],
'decimal' => [
'name' => 'decimal',
'scale' => 0,
'precision' => 10,
],
'datetime' => [ 'name' => 'datetime' ],
'timestamp' => [ 'name' => 'timestamp' ],
'time' => [ 'name' => 'time' ],
'date' => [ 'name' => 'date' ],
'binary' => [ 'name' => 'blob' ],
'tinybinary' => [ 'name' => 'tinyblob' ],
'mediumbinary' => [ 'name' => 'mediumblob' ],
'longbinary' => [ 'name' => 'longblob' ],
'boolean' => [
'name' => 'tinyint',
'limit' => 1,
],
'enum' => [
'name' => 'enum',
'values' => [],
],
'uuid' => [
'name' => 'char',
'limit' => 36,
],
'char' => [ 'name' => 'char' ],
];
return $types;
}
/**
* Checks if a table exists.
*
* @param string $table The table name.
*
* @return bool
*/
public function has_table( $table ) {
return $this->table_exists( $table );
}
/**
* Allows overriding the hardcoded schema table name constant in case of parallel migrations.
*
* @return string
*/
public function get_schema_version_table_name() {
return Model::get_table_name( 'migrations' );
}
/**
* Create the schema table, if necessary.
*
* @return void
*/
public function create_schema_version_table() {
if ( ! $this->has_table( $this->get_schema_version_table_name() ) ) {
$t = $this->create_table( $this->get_schema_version_table_name() );
$t->column( 'version', 'string', [ 'limit' => 191 ] );
$t->finish();
$this->add_index( $this->get_schema_version_table_name(), 'version', [ 'unique' => true ] );
}
}
/**
* Starts a transaction.
*
* @return void
*/
public function start_transaction() {
if ( $this->in_transaction() === false ) {
$this->begin_transaction();
}
}
/**
* Commits a transaction.
*
* @return void
*/
public function commit_transaction() {
if ( $this->in_transaction() ) {
$this->commit();
}
}
/**
* Rollbacks a transaction.
*
* @return void
*/
public function rollback_transaction() {
if ( $this->in_transaction() ) {
$this->rollback();
}
}
/**
* Quotes a table name string.
*
* @param string $text Table name.
*
* @return string
*/
public function quote_table( $text ) {
return '`' . $text . '`';
}
/**
* Return the SQL definition of a column.
*
* @param string $column_name The column name.
* @param string $type The type of the column.
* @param array|null $options Column options.
*
* @return string
*/
public function column_definition( $column_name, $type, $options = null ) {
$col = new Column( $this, $column_name, $type, $options );
return $col->__toString();
}
/**
* Checks if a database exists.
*
* @param string $database The database name.
*
* @return bool
*/
public function database_exists( $database ) {
$ddl = 'SHOW DATABASES';
$result = $this->select_all( $ddl );
if ( \count( $result ) === 0 ) {
return false;
}
foreach ( $result as $dbrow ) {
if ( $dbrow['Database'] === $database ) {
return true;
}
}
return false;
}
/**
* Creates a database.
*
* @param string $db The database name.
*
* @return bool
*/
public function create_database( $db ) {
if ( $this->database_exists( $db ) ) {
return false;
}
$ddl = \sprintf( 'CREATE DATABASE %s', $this->identifier( $db ) );
$result = $this->query( $ddl );
return $result === true;
}
/**
* Drops a database.
*
* @param string $db The database name.
*
* @return bool
*/
public function drop_database( $db ) {
if ( ! $this->database_exists( $db ) ) {
return false;
}
$ddl = \sprintf( 'DROP DATABASE IF EXISTS %s', $this->identifier( $db ) );
$result = $this->query( $ddl );
return $result === true;
}
/**
* Checks if a table exists.
*
* @param string $table The table name.
*
* @return bool
*/
public function table_exists( $table ) {
global $wpdb;
// We need last error to be clear so we can check against it easily.
$previous_last_error = $wpdb->last_error;
$previous_suppress_errors = $wpdb->suppress_errors;
$wpdb->last_error = '';
$wpdb->suppress_errors = true;
$result = $wpdb->query( "SELECT * FROM $table LIMIT 1" );
// Restore the last error, as this is not truly an error and we don't want to alarm people.
$wpdb->last_error = $previous_last_error;
$wpdb->suppress_errors = $previous_suppress_errors;
return $result !== false;
}
/**
* Wrapper to execute a query.
*
* @param string $query The query to run.
*
* @return bool
*/
public function execute( $query ) {
return $this->query( $query );
}
/**
* Executes a query.
*
* @param string $query The query to run.
*
* @return bool Whether or not the query was performed succesfully.
*/
public function query( $query ) {
global $wpdb;
$query_type = $this->determine_query_type( $query );
$data = [];
if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) {
$data = $wpdb->get_results( $query, \ARRAY_A );
if ( $data === false ) {
return false;
}
return $data;
}
else {
// INSERT, DELETE, etc...
$result = $wpdb->query( $query );
if ( $result === false ) {
return false;
}
if ( $query_type === Constants::SQL_INSERT ) {
return $wpdb->insert_id;
}
return true;
}
}
/**
* Returns a single result for a query.
*
* @param string $query The query to run.
*
* @return array|false An associative array of the result.
*/
public function select_one( $query ) {
global $wpdb;
$query_type = $this->determine_query_type( $query );
if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) {
$result = $wpdb->query( $query );
if ( $result === false ) {
return false;
}
return $wpdb->last_result[0];
}
return false;
}
/**
* Returns all results for a query.
*
* @param string $query The query to run.
*
* @return array An array of associative arrays.
*/
public function select_all( $query ) {
return $this->query( $query );
}
/**
* Use this method for non-SELECT queries.
* Or anything where you dont necessarily expect a result string, e.g. DROPs, CREATEs, etc.
*
* @param string $ddl The query to run.
*
* @return bool
*/
public function execute_ddl( $ddl ) {
return $this->query( $ddl );
}
/**
* Drops a table
*
* @param string $table The table name.
*
* @return bool Whether or not the table was succesfully dropped.
*/
public function drop_table( $table ) {
$ddl = \sprintf( 'DROP TABLE IF EXISTS %s', $this->identifier( $table ) );
return $this->query( $ddl );
}
/**
* Creates a table.
*
* @param string $table_name The table name.
* @param array $options The options.
*
* @return Table
*/
public function create_table( $table_name, $options = [] ) {
return new Table( $this, $table_name, $options );
}
/**
* Escapes a string for usage in queries.
*
* @param string $text The string.
*
* @return string
*/
public function quote_string( $text ) {
global $wpdb;
return $wpdb->_escape( $text );
}
/**
* Returns a quoted string.
*
* @param string $text The string.
*
* @return string
*/
public function identifier( $text ) {
return '`' . $text . '`';
}
/**
* Renames a table.
*
* @param string $name The current table name.
* @param string $new_name The new table name.
*
* @return bool
*/
public function rename_table( $name, $new_name ) {
if ( empty( $name ) || empty( $new_name ) ) {
return false;
}
$sql = \sprintf( 'RENAME TABLE %s TO %s', $this->identifier( $name ), $this->identifier( $new_name ) );
return $this->execute_ddl( $sql );
}
/**
* Adds a column.
*
* @param string $table_name The table name.
* @param string $column_name The column name.
* @param string $type The column type.
* @param array $options Column options.
*
* @return bool
*/
public function add_column( $table_name, $column_name, $type, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) {
return false;
}
// Default types.
if ( ! \array_key_exists( 'limit', $options ) ) {
$options['limit'] = null;
}
if ( ! \array_key_exists( 'precision', $options ) ) {
$options['precision'] = null;
}
if ( ! \array_key_exists( 'scale', $options ) ) {
$options['scale'] = null;
}
$sql = \sprintf( 'ALTER TABLE %s ADD `%s` %s', $this->identifier( $table_name ), $column_name, $this->type_to_sql( $type, $options ) );
$sql .= $this->add_column_options( $type, $options );
return $this->execute_ddl( $sql );
}
/**
* Drops a column.
*
* @param string $table_name The table name.
* @param string $column_name The column name.
*
* @return bool
*/
public function remove_column( $table_name, $column_name ) {
$sql = \sprintf( 'ALTER TABLE %s DROP COLUMN %s', $this->identifier( $table_name ), $this->identifier( $column_name ) );
return $this->execute_ddl( $sql );
}
/**
* Renames a column.
*
* @param string $table_name The table name.
* @param string $column_name The column name.
* @param string $new_column_name The new column name.
*
* @return bool
*/
public function rename_column( $table_name, $column_name, $new_column_name ) {
if ( empty( $table_name ) || empty( $column_name ) || empty( $new_column_name ) ) {
return false;
}
$column_info = $this->column_info( $table_name, $column_name );
$current_type = $column_info['type'];
$sql = \sprintf( 'ALTER TABLE %s CHANGE %s %s %s', $this->identifier( $table_name ), $this->identifier( $column_name ), $this->identifier( $new_column_name ), $current_type );
$sql .= $this->add_column_options( $current_type, $column_info );
return $this->execute_ddl( $sql );
}
/**
* Changes a column.
*
* @param string $table_name The table name.
* @param string $column_name The column name.
* @param string $type The column type.
* @param array $options Column options.
*
* @return bool
*/
public function change_column( $table_name, $column_name, $type, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) {
return false;
}
$column_info = $this->column_info( $table_name, $column_name );
// Default types.
if ( ! \array_key_exists( 'limit', $options ) ) {
$options['limit'] = null;
}
if ( ! \array_key_exists( 'precision', $options ) ) {
$options['precision'] = null;
}
if ( ! \array_key_exists( 'scale', $options ) ) {
$options['scale'] = null;
}
$sql = \sprintf( 'ALTER TABLE `%s` CHANGE `%s` `%s` %s', $table_name, $column_name, $column_name, $this->type_to_sql( $type, $options ) );
$sql .= $this->add_column_options( $type, $options );
return $this->execute_ddl( $sql );
}
/**
* Returns the database information for a column.
*
* @param string $table The table name.
* @param string $column The column name.
*
* @return array|null
*/
public function column_info( $table, $column ) {
if ( empty( $table ) || empty( $column ) ) {
return null;
}
try {
$sql = \sprintf( "SHOW FULL COLUMNS FROM %s LIKE '%s'", $this->identifier( $table ), $column );
$result = $this->select_one( $sql );
if ( \is_array( $result ) ) {
$result = \array_change_key_case( $result, \CASE_LOWER );
}
return $result;
} catch ( Exception $e ) {
return null;
}
}
/**
* Adds an index.
*
* @param string $table_name The table name.
* @param array|string $column_name The column name(s).
* @param array $options Index options.
*
* @return bool
*/
public function add_index( $table_name, $column_name, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) ) {
return false;
}
// Unique index?
if ( \is_array( $options ) && \array_key_exists( 'unique', $options ) && $options['unique'] === true ) {
$unique = true;
}
else {
$unique = false;
}
// Did the user specify an index name?
if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
$index_name = $options['name'];
}
else {
$index_name = $this->get_index_name( $table_name, $column_name );
}
if ( \strlen( $index_name ) > Constants::MYSQL_MAX_IDENTIFIER_LENGTH ) {
return false;
}
if ( ! \is_array( $column_name ) ) {
$column_names = [ $column_name ];
}
else {
$column_names = $column_name;
}
$cols = [];
foreach ( $column_names as $name ) {
$cols[] = $this->identifier( $name );
}
$sql = \sprintf(
'CREATE %sINDEX %s ON %s(%s)',
( $unique === true ) ? 'UNIQUE ' : '',
$this->identifier( $index_name ),
$this->identifier( $table_name ),
\implode( ', ', $cols ),
);
return $this->execute_ddl( $sql );
}
/**
* Drops an index.
*
* @param string $table_name The table name.
* @param array|string $column_name The column name(s).
* @param array $options Index options.
*
* @return bool
*/
public function remove_index( $table_name, $column_name, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) ) {
return false;
}
// Did the user specify an index name?
if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
$index_name = $options['name'];
}
else {
$index_name = $this->get_index_name( $table_name, $column_name );
}
$sql = \sprintf( 'DROP INDEX %s ON %s', $this->identifier( $index_name ), $this->identifier( $table_name ) );
return $this->execute_ddl( $sql );
}
/**
* Adds timestamps.
*
* @param string $table_name The table name.
* @param string $created_column_name Created at column name.
* @param string $updated_column_name Updated at column name.
*
* @return bool
*/
public function add_timestamps( $table_name, $created_column_name, $updated_column_name ) {
if ( empty( $table_name ) || empty( $created_column_name ) || empty( $updated_column_name ) ) {
return false;
}
$created_at = $this->add_column( $table_name, $created_column_name, 'datetime' );
$updated_at = $this->add_column(
$table_name,
$updated_column_name,
'timestamp',
[
'null' => false,
'default' => 'CURRENT_TIMESTAMP',
'extra' => 'ON UPDATE CURRENT_TIMESTAMP',
],
);
return $created_at && $updated_at;
}
/**
* Removes timestamps.
*
* @param string $table_name The table name.
* @param string $created_column_name Created at column name.
* @param string $updated_column_name Updated at column name.
*
* @return bool Whether or not the timestamps were removed.
*/
public function remove_timestamps( $table_name, $created_column_name, $updated_column_name ) {
if ( empty( $table_name ) || empty( $created_column_name ) || empty( $updated_column_name ) ) {
return false;
}
$updated_at = $this->remove_column( $table_name, $created_column_name );
$created_at = $this->remove_column( $table_name, $updated_column_name );
return $created_at && $updated_at;
}
/**
* Checks an index.
*
* @param string $table_name The table name.
* @param array|string $column_name The column name(s).
* @param array $options Index options.
*
* @return bool Whether or not the index exists.
*/
public function has_index( $table_name, $column_name, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) ) {
return false;
}
// Did the user specify an index name?
if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
$index_name = $options['name'];
}
else {
$index_name = $this->get_index_name( $table_name, $column_name );
}
$indexes = $this->indexes( $table_name );
foreach ( $indexes as $idx ) {
if ( $idx['name'] === $index_name ) {
return true;
}
}
return false;
}
/**
* Returns all indexes of a table.
*
* @param string $table_name The table name.
*
* @return array
*/
public function indexes( $table_name ) {
$sql = \sprintf( 'SHOW KEYS FROM %s', $this->identifier( $table_name ) );
$result = $this->select_all( $sql );
$indexes = [];
foreach ( $result as $row ) {
// Skip primary.
if ( $row['Key_name'] === 'PRIMARY' ) {
continue;
}
$indexes[] = [
'name' => $row['Key_name'],
'unique' => (int) $row['Non_unique'] === 0,
];
}
return $indexes;
}
/**
* Converts a type to sql. Default options:
* $limit = null, $precision = null, $scale = null
*
* @param string $type The native type.
* @param array $options The options.
*
* @return string The SQL type.
*
* @throws Exception If invalid arguments are supplied.
*/
public function type_to_sql( $type, $options = [] ) {
$natives = $this->native_database_types();
if ( ! \array_key_exists( $type, $natives ) ) {
$error = \sprintf( "Error:I dont know what column type of '%s' maps to for MySQL.", $type );
$error .= "\nYou provided: {$type}\n";
$error .= "Valid types are: \n";
$types = \array_keys( $natives );
foreach ( $types as $t ) {
if ( $t === 'primary_key' ) {
continue;
}
$error .= "\t{$t}\n";
}
throw new Exception( $error );
}
$scale = null;
$precision = null;
$limit = null;
if ( isset( $options['precision'] ) ) {
$precision = $options['precision'];
}
if ( isset( $options['scale'] ) ) {
$scale = $options['scale'];
}
if ( isset( $options['limit'] ) ) {
$limit = $options['limit'];
}
if ( isset( $options['values'] ) ) {
$values = $options['values'];
}
$native_type = $natives[ $type ];
if ( \is_array( $native_type ) && \array_key_exists( 'name', $native_type ) ) {
$column_type_sql = $native_type['name'];
}
else {
return $native_type;
}
if ( $type === 'decimal' || $type === 'float' ) {
// Ignore limit, use precison and scale.
if ( $precision === null && \array_key_exists( 'precision', $native_type ) ) {
$precision = $native_type['precision'];
}
if ( $scale === null && \array_key_exists( 'scale', $native_type ) ) {
$scale = $native_type['scale'];
}
if ( $precision !== null ) {
if ( \is_int( $scale ) ) {
$column_type_sql .= \sprintf( '(%d, %d)', $precision, $scale );
}
else {
$column_type_sql .= \sprintf( '(%d)', $precision );
}
}
elseif ( $scale ) {
throw new Exception( "Error adding $type column: precision cannot be empty if scale is specified" );
}
}
elseif ( $type === 'enum' ) {
if ( empty( $values ) ) {
throw new Exception( 'Error adding enum column: there must be at least one value defined' );
}
else {
$column_type_sql .= \sprintf(
"('%s')",
\implode( "','", \array_map( [ $this, 'quote_string' ], $values ) ),
);
}
}
// Not a decimal column.
if ( $limit === null && \array_key_exists( 'limit', $native_type ) ) {
$limit = $native_type['limit'];
}
if ( $limit ) {
$column_type_sql .= \sprintf( '(%d)', $limit );
}
return $column_type_sql;
}
/**
* Adds column options.
*
* @param string $type The native type.
* @param array $options The options.
*
* @return string The SQL statement for the column options.
*
* @throws Exception If invalid arguments are supplied.
*/
public function add_column_options( $type, $options ) {
$sql = '';
if ( ! \is_array( $options ) ) {
return $sql;
}
if ( \array_key_exists( 'unsigned', $options ) && $options['unsigned'] === true ) {
$sql .= ' UNSIGNED';
}
if ( \array_key_exists( 'character', $options ) ) {
$sql .= \sprintf( ' CHARACTER SET %s', $this->identifier( $options['character'] ) );
}
if ( \array_key_exists( 'collate', $options ) ) {
$sql .= \sprintf( ' COLLATE %s', $this->identifier( $options['collate'] ) );
}
if ( \array_key_exists( 'auto_increment', $options ) && $options['auto_increment'] === true ) {
$sql .= ' auto_increment';
}
if ( \array_key_exists( 'default', $options ) && $options['default'] !== null ) {
if ( $this->is_sql_method_call( $options['default'] ) ) {
throw new Exception( 'MySQL does not support function calls as default values, constants only.' );
}
if ( \is_int( $options['default'] ) ) {
$default_format = '%d';
}
elseif ( \is_bool( $options['default'] ) ) {
$default_format = "'%d'";
}
elseif ( $options['default'] === 'CURRENT_TIMESTAMP' ) {
$default_format = '%s';
}
else {
$default_format = "'%s'";
}
$default_value = \sprintf( $default_format, $options['default'] );
$sql .= \sprintf( ' DEFAULT %s', $default_value );
}
if ( \array_key_exists( 'null', $options ) ) {
if ( $options['null'] === false || $options['null'] === 'NO' ) {
$sql .= ' NOT NULL';
}
elseif ( $type === 'timestamp' ) {
$sql .= ' NULL';
}
}
if ( \array_key_exists( 'comment', $options ) ) {
$sql .= \sprintf( " COMMENT '%s'", $this->quote_string( $options['comment'] ) );
}
if ( \array_key_exists( 'extra', $options ) ) {
$sql .= \sprintf( ' %s', $this->quote_string( $options['extra'] ) );
}
if ( \array_key_exists( 'after', $options ) ) {
$sql .= \sprintf( ' AFTER %s', $this->identifier( $options['after'] ) );
}
return $sql;
}
/**
* Returns a list of all versions that have been migrated.
*
* @return string[] The version numbers that have been migrated.
*/
public function get_migrated_versions() {
$result = $this->select_all( \sprintf( 'SELECT version FROM %s', $this->get_schema_version_table_name() ) );
return \array_column( $result, 'version' );
}
/**
* Adds a migrated version.
*
* @param string $version The version.
*
* @return bool Whether or not the version was succesfully set.
*/
public function add_version( $version ) {
$sql = \sprintf( "INSERT INTO %s (version) VALUES ('%s')", $this->get_schema_version_table_name(), $version );
return $this->execute_ddl( $sql );
}
/**
* Removes a migrated version.
*
* @param string $version The version.
*
* @return bool Whether or not the version was succesfully removed.
*/
public function remove_version( $version ) {
$sql = \sprintf( "DELETE FROM %s WHERE version = '%s'", $this->get_schema_version_table_name(), $version );
return $this->execute_ddl( $sql );
}
/**
* Returns a message displaying the current version
*
* @return string
*/
public function __toString() {
return self::class . ', version ' . $this->version;
}
/**
* Returns an index name.
*
* @param string $table_name The table name.
* @param string $column_name The column name.
*
* @return string The index name.
*/
private function get_index_name( $table_name, $column_name ) {
$name = \preg_replace( '/\\W/', '_', $table_name );
$name = \preg_replace( '/\\_{2,}/', '_', $name );
// If the column parameter is an array then the user wants to create a multi-column index.
if ( \is_array( $column_name ) ) {
$column_str = \implode( '_and_', $column_name );
}
else {
$column_str = $column_name;
}
$name .= \sprintf( '_%s', $column_str );
return $name;
}
/**
* Returns the type of a query.
*
* @param string $query The query to run.
*
* @return int The query type.
*/
private function determine_query_type( $query ) {
$query = \strtolower( \trim( $query ) );
$match = [];
\preg_match( '/^(\\w)*/i', $query, $match );
$type = $match[0];
switch ( $type ) {
case 'select':
return Constants::SQL_SELECT;
case 'update':
return Constants::SQL_UPDATE;
case 'delete':
return Constants::SQL_DELETE;
case 'insert':
return Constants::SQL_INSERT;
case 'alter':
return Constants::SQL_ALTER;
case 'drop':
return Constants::SQL_DROP;
case 'create':
return Constants::SQL_CREATE;
case 'show':
return Constants::SQL_SHOW;
case 'rename':
return Constants::SQL_RENAME;
case 'set':
return Constants::SQL_SET;
default:
return Constants::SQL_UNKNOWN_QUERY_TYPE;
}
}
/**
* Detect whether or not the string represents a function call and if so
* do not wrap it in single-quotes, otherwise do wrap in single quotes.
*
* @param string $text The string.
*
* @return bool Whether or not it's a SQL function call.
*/
private function is_sql_method_call( $text ) {
$text = \trim( $text );
if ( \substr( $text, -2, 2 ) === '()' ) {
return true;
}
return false;
}
/**
* Checks if a transaction is active.
*
* @return bool
*/
private function in_transaction() {
return $this->in_transaction;
}
/**
* Starts a transaction.
*
* @return void
*
* @throws Exception If a transaction was already started.
*/
private function begin_transaction() {
global $wpdb;
if ( $this->in_transaction === true ) {
throw new Exception( 'Transaction already started' );
}
$wpdb->query( 'START TRANSACTION' );
$this->in_transaction = true;
}
/**
* Commits a transaction.
*
* @return void
*
* @throws Exception If no transaction was strated.
*/
private function commit() {
global $wpdb;
if ( $this->in_transaction === false ) {
throw new Exception( 'Transaction not started' );
}
$wpdb->query( 'COMMIT' );
$this->in_transaction = false;
}
/**
* Rollbacks a transaction.
*
* @return void
*
* @throws Exception If no transaction was started.
*/
private function rollback() {
global $wpdb;
if ( $this->in_transaction === false ) {
throw new Exception( 'Transaction not started' );
}
$wpdb->query( 'ROLLBACK' );
$this->in_transaction = false;
}
}
Home - Capital GMC Buick Regina
Skip to content
{{ $t(category) }}
Error
{{vehicle.modelData.year}} {{vehicle.modelData.make}} {{vehicle.modelData.model}}
Starting from {{vehicle.modelData.startingPrice | moneyFormat(lang)}}
Welcome to Capital GMC BUICK – REGINA
Thank you for choosing Capital GMC Buick | Regina, your premier certified Buick and GMC dealership proudly serving drivers in Regina and the surrounding communities. Whether you’re searching for a brand-new Buick or GMC vehicle or a meticulously inspected pre-owned model, we have a diverse selection to match your needs and lifestyle.
Beyond our impressive inventory, we offer a seamless and stress-free financing experience through our well-connected finance centre, where our team of experts is dedicated to securing the best loan or lease options for you, quickly, transparently, and hassle-free.
But our commitment to you doesn’t stop at the sale. Our state-of-the-art service centre is staffed with skilled Buick and GMC technicians who use the latest equipment and genuine OEM parts to keep your vehicle running at its best. From routine maintenance to complex repairs, we’ve got you covered.
Experience top-tier customer service, quality vehicles, and expert care, all in one place. Visit Capital GMC Buick | Regina today or call us at 306-205-8072 with any questions. We’re here to help!
Ask a Question
Capital GMC Buick – Regina
Contact Us
Have a question or need assistance? Fill out the form and we will reach out to you as soon as possible.
Notice: JavaScript is required for this content.
CLOSE
Schedule a Visit
Let us know when you are coming and how we can assist you. We can ensure someone will be on hand to help you out at the desired date and time.
Notice: JavaScript is required for this content.
CLOSE
Find a Career
Have a look at our list of available positions and apply online today to join our team!
×
Opportunities to Grow
The auto industry is constantly changing and we want to continue to grow. We offer growth, leadership & mentorship programs to allow our staff to grow with us.
×
Competitive Salary
We have a significant earning potential with incentive-based pay in most roles. We also offer an employee referral bonus with paid bonuses.
×
Health & Dental
We offer a comprehensive benefits package including extended health, dental, and vision care. We also include paramedical, life insurance, paid sick leave, short & long-term disability coverages.
×
Vacation
We value our employees and want everyone to take their vacation time. We offer a minimum of 2 weeks vacation each year.
×
Training & Development
We have many opportunities for paid education and training in-house as well as training from the Manufacturer.
×
$10,000 Cash Giveaway – Terms & Conditions
All October long, stop by Capital GMC Buick Cadillac, to enter for your chance to win $10,000 cash. No purchase is required, but entries must be made in-store.
The contest is open to residents of Saskatchewan who are 18+. Dealership employees and their households are not eligible. Entries will be accepted from October 1 to October 31, 2025. A random draw will take place on November 1, 2025.
The prize is one $10,000 award, paid by cheque, and must be accepted as awarded. Winner will be contacted by phone or email and must respond within 7 days or another entry may be drawn. Odds of winning depend on the number of entries received.By entering, you agree that Capital Automotive Group may use your name and photo for winner announcements. The contest is governed by the laws of Saskatchewan.
Notice: JavaScript is required for this content.
CLOSE