Skip to content

Instantly share code, notes, and snippets.

@ceeram
Created July 6, 2012 21:05
Show Gist options
  • Save ceeram/3062745 to your computer and use it in GitHub Desktop.
Save ceeram/3062745 to your computer and use it in GitHub Desktop.
Odbc datasource CakePHP for Throughbred odbc connection. UNSTABLE
<?php
/**
* ODBC for DBO
*
* PHP versions 4 and 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package datasources
* @subpackage datasources.models.datasources.dbo
* @since CakePHP Datasources v 0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::uses('DboSource', 'Model/Datasource');
/**
* Short description for class.
*
* Long description for class
*
* @package cake
* @subpackage cake.cake.libs.model.datasources.dbo
*/
class Odbc extends DboSource {
/**
* Driver description
*
* @var string
*/
public $description = "ODBC DBO Driver";
/**
* Database keyword used to assign aliases to identifiers.
*
* @var string
*/
public $alias = "";
/**
* Table/column starting quote
*
* @var string
*/
public $startQuote = "";
/**
* Table/column end quote
*
* @var string
*/
public $endQuote = "";
/**
* Columns
*
* @var array
*/
//var $columns = array();
var $columns = array('primary_key' => array('name' => 'int(11) DEFAULT NULL auto_increment'),
'string' => array('name' => 'varchar', 'limit' => '255'),
'text' => array('name' => 'text'),
'integer' => array('name' => 'int', 'limit' => '11'),
'float' => array('name' => 'float'),
'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d h:i:s', 'formatter' => 'date'),
'timestamp' => array('name' => 'datetime', 'format' => 'Y-m-d h:i:s', 'formatter' => 'date'),
'time' => array('name' => 'time', 'format' => 'h:i:s', 'formatter' => 'date'),
'date' => array('name' => 'date', 'format' => 'Y-m-d', 'formatter' => 'date'),
'binary' => array('name' => 'blob'),
'boolean' => array('name' => 'tinyint', 'limit' => '1'));
/**
* Whether or not to cache the results of DboSource::name() and DboSource::conditions()
* into the memory cache. Set to false to disable the use of the memory cache.
*
* @var boolean.
*/
public $cacheMethods = true;
/**
* Connects to the database using options in the given configuration array.
*
* @return boolean True if the database could be connected, else false
*/
public function connect() {
$this->config;
$this->connected = false;
try {
$flags = array(
PDO::ATTR_PERSISTENT => $this->config['persistent'],
PDO::ATTR_EMULATE_PREPARES => true,
);
if (!empty($this->config['encoding'])) {
//$flags[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $config['encoding'];
}
$this->_connection = new PDO(
"odbc:{$this->config['driver']}", null, null, $flags
);
$this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->connected = true;
} catch (PDOException $e) {
throw new MissingConnectionException(array('class' => $e->getMessage()));
}
return $this->connected;
}
/**
* Check if the ODBC extension is installed/loaded
*
* @return boolean
*/
public function enabled() {
return in_array('odbc', PDO::getAvailableDrivers());
}
/**
* Returns an array of sources (tables) in the database.
*
* @return array Array of tablenames in the database
*/
function listSources() {
$cache = parent::listSources();
if ($cache != null) {
return $cache;
}
$result = $this->_connection->query("SELECT * FROM ODLQTBLE");
$tables = array_map('trim', $result->fetchAll(PDO::FETCH_COLUMN, 1));
unset($result);
parent::listSources($tables);
return $tables;
}
/**
* Returns an array of the fields in given table name.
*
* @param Model $model Model object to describe
* @return array Fields in table. Keys are name and type
*/
function describe($model) {
$cache = parent::describe($model);
if ($cache != null) {
//return $cache;
}
$fields = array();
$sql = 'SELECT * FROM ' . $this->fullTableName($model, false);
$fields = array();
try{
$result = $this->_connection->query($sql);
} catch (PDOException $e) {
debug($e->getMessage());
}
if ($result instanceof PDOStatement == false) {
return $fields;
}
$row = $result->fetch(PDO::FETCH_ASSOC);
if (!is_array($row)) {
return $fields;
}
unset($result);
$cols = array_keys($row);
foreach ($cols as $column) {
$fields[$column] = array('type' => 'text', 'null' => true);
}
$this->_cacheDescription($model->tablePrefix . $model->table, $fields);
return $fields;
}
/**
* Enter description here...
*
* @param unknown_type $results
*/
public function resultSet(&$results) {
$this->map = array();
$clean = substr($results->queryString, strpos($results->queryString, " ") + 1);
$clean = substr($clean, 0, strpos($clean, ' FROM') - strlen($clean));
$parts = explode(", ", $clean);
foreach ($parts as $key => $value) {
list($table, $name) = pluginSplit($value, false, 0);
if (!$table && strpos($name, $this->virtualFieldSeparator) !== false) {
$name = substr(strrchr($name, " "), 1);
}
$this->map[$key] = array($table, $name, "VAR_STRING");
}
}
/**
* Fetches the next row from the current result set
*
* @return unknown
*/
/**
* Fetches the next row from the current result set
*
* @return mixed array with results fetched and mapped to column names or false if there is no results left to fetch
*/
public function fetchResult() {
if ($row = $this->_result->fetch()) {
$resultRow = array();
foreach ($this->map as $col => $meta) {
list($table, $column, $type) = $meta;
if (strpos($column,'COUNT(')!== false) {
$column = 'count';
}
$resultRow[$table][$column] = trim($row[$col]);
if ($type === 'boolean' && !is_null($row[$col])) {
$resultRow[$table][$column] = $this->boolean($resultRow[$table][$column]);
}
}
return $resultRow;
}
$this->_result->closeCursor();
return false;
}
/**
* Returns a limit statement in the correct format for the particular database.
*
* @param integer $limit Limit of results returned
* @param integer $offset Offset from which to start results
* @return string SQL limit/offset statement
*/
public function limit($limit, $offset = null) {
return null;
}
/**
* Returns an SQL calculation, i.e. COUNT() or MAX()
*
* @param model $model
* @param string $func Lowercase name of SQL function, i.e. 'count' or 'max'
* @param array $params Function parameters (any values must be quoted manually)
* @return string An SQL calculation function
*/
public function calculate($model, $func, $params = array()) {
$params = (array)$params;
switch (strtolower($func)) {
case 'count':
if (!isset($params[0])) {
$params[0] = '*';
}
if (!isset($params[1])) {
$params[1] = 'count';
}
if (is_object($model) && $model->isVirtualField($params[0])){
$arg = $this->__quoteFields($model->getVirtualField($params[0]));
} else {
$arg = $this->name($params[0]);
}
return "COUNT($arg) AS '$params[1]'";
case 'max':
case 'min':
if (!isset($params[1])) {
$params[1] = $params[0];
}
if (is_object($model) && $model->isVirtualField($params[0])) {
$arg = $this->__quoteFields($model->getVirtualField($params[0]));
} else {
$arg = $this->name($params[0]);
}
return strtoupper($func) . '(' . $arg . ') AS ' . $this->name($params[1]);
break;
}
}
/**
* Returns a quoted and escaped string of $data for use in an SQL statement.
*
* @param string $data String to be prepared for use in an SQL statement
* @param string $column The column into which this data will be inserted
* @return string Quoted and escaped data
*/
public function value($data, $column = null) {
if (is_array($data) && !empty($data)) {
return array_map(
array(&$this, 'value'),
$data, array_fill(0, count($data), $column)
);
} elseif (is_object($data) && isset($data->type, $data->value)) {
if ($data->type == 'identifier') {
return $this->name($data->value);
} elseif ($data->type == 'expression') {
return $data->value;
}
} elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
return $data;
}
if ($data === null || (is_array($data) && empty($data))) {
return 'NULL';
}
if (empty($column)) {
$column = $this->introspectType($data);
}
switch ($column) {
case 'binary':
return $this->_connection->quote($data, PDO::PARAM_LOB);
break;
case 'boolean':
return $this->_connection->quote($this->boolean($data, true), PDO::PARAM_BOOL);
break;
case 'string':
case 'text':
if ((is_int($data) || $data === '0') || (
is_numeric($data) && strpos($data, ',') === false &&
$data[0] != '0' && strpos($data, 'e') === false)
) {
//return $data;
}
return "'$data'";
default:
if ($data === '') {
return 'NULL';
}
if (is_float($data)) {
return sprintf('%F', $data);
}
if ((is_int($data) || $data === '0') || (
is_numeric($data) && strpos($data, ',') === false &&
$data[0] != '0' && strpos($data, 'e') === false)
) {
//return $data;
}
return "'$data'";
break;
}
}
/**
* Builds and generates an SQL statement from an array. Handles final clean-up before conversion.
*
* @param array $query An array defining an SQL query
* @param Model $model The model object which initiated the query
* @return string An executable SQL statement
* @see DboSource::renderStatement()
*/
public function buildStatement($query, $model) {
$query = array_merge(array('offset' => null, 'joins' => array()), $query);
if (!empty($query['joins'])) {
$count = count($query['joins']);
for ($i = 0; $i < $count; $i++) {
if (is_array($query['joins'][$i])) {
$query['conditions'][] = $query['joins'][$i]['conditions'];
}
}
}
return parent::buildStatement($query, $model);
}
/**
* Renders a final SQL JOIN statement
*
* @param array $data
* @return string
*/
public function renderJoinStatement($data) {
extract($data);
return trim(", {$table} {$alias}");
}
/**
* Generates the fields list of an SQL query.
*
* @param Model $model
* @param string $alias Alias table name
* @param mixed $fields
* @param boolean $quote If false, returns fields array unquoted
* @return array
*/
public function fields(Model $model, $alias = null, $fields = array(), $quote = true) {
if (empty($fields) && !$model->schema(true)) {
$fields = '*';
}
return parent::fields($model, $alias, $fields, $quote);
}
///**
// * Creates a WHERE clause by parsing given conditions array. Used by DboSource::conditions().
// *
// * @param array $conditions Array or string of conditions
// * @param boolean $quoteValues If true, values should be quoted
// * @param Model $model A reference to the Model instance making the query
// * @return string SQL fragment
// */
// public function conditionKeysToString($conditions, $quoteValues = true, $model = null) {
// $out = array();
// $data = $columnType = null;
// $bool = array('and', 'or', 'not', 'and not', 'or not', 'xor', '||', '&&');
//
// foreach ($conditions as $key => $value) {
// $join = ' AND ';
// $not = null;
//
// if (is_array($value)) {
// $valueInsert = (
// !empty($value) &&
// (substr_count($key, '?') === count($value) || substr_count($key, ':') === count($value))
// );
// }
//
// if (is_numeric($key) && empty($value)) {
// continue;
// } elseif (is_numeric($key) && is_string($value)) {
// $out[] = $not . $this->_quoteFields($value);
// } elseif ((is_numeric($key) && is_array($value)) || in_array(strtolower(trim($key)), $bool)) {
// if (in_array(strtolower(trim($key)), $bool)) {
// $join = ' ' . strtoupper($key) . ' ';
// } else {
// $key = $join;
// }
// $value = $this->conditionKeysToString($value, $quoteValues, $model);
//
// if (strpos($join, 'NOT') !== false) {
// if (strtoupper(trim($key)) === 'NOT') {
// $key = 'AND ' . trim($key);
// }
// $not = 'NOT ';
// }
//
// if (empty($value[1])) {
// if ($not) {
// $out[] = $not . '(' . $value[0] . ')';
// } else {
// $out[] = $value[0] ;
// }
// } else {
// $out[] = '(' . $not . '(' . implode(') ' . strtoupper($key) . ' (', $value) . '))';
// }
// } else {
// if (is_object($value) && isset($value->type)) {
// if ($value->type === 'identifier') {
// $data .= $this->name($key) . ' = ' . $this->name($value->value);
// } elseif ($value->type === 'expression') {
// if (is_numeric($key)) {
// $data .= $value->value;
// } else {
// $data .= $this->name($key) . ' LIKE ' . $value->value;
// }
// }
// } elseif (is_array($value) && !empty($value) && !$valueInsert) {
// $keys = array_keys($value);
// if ($keys === array_values($keys)) {
// $count = count($value);
// if ($count === 1) {
// $data = $this->_quoteFields($key) . ' LIKE ';
// $close = false;
// } else {
// $data = $this->_quoteFields($key) . ' IN (';
// }
// if ($quoteValues) {
// if (is_object($model)) {
// $columnType = $model->getColumnType($key);
// }
// $data .= implode(', ', $this->value($value, $columnType));
// }
// if (!empty($close)) {
// $data .= ')';
// }
// } else {
// $ret = $this->conditionKeysToString($value, $quoteValues, $model);
// if (count($ret) > 1) {
// $data = '(' . implode(') AND (', $ret) . ')';
// } elseif (isset($ret[0])) {
// $data = $ret[0];
// }
// }
// } elseif (is_numeric($key) && !empty($value)) {
// $data = $this->_quoteFields($value);
// } else {
// $data = $this->_parseKey($model, trim($key), $value);
// }
//
// if ($data != null) {
// $out[] = $data;
// $data = null;
// }
// }
// }
// return $out;
// }
//
///**
// * Extracts a Model.field identifier and an SQL condition operator from a string, formats
// * and inserts values, and composes them into an SQL snippet.
// *
// * @param Model $model Model object initiating the query
// * @param string $key An SQL key snippet containing a field and optional SQL operator
// * @param mixed $value The value(s) to be inserted in the string
// * @return string
// */
// protected function _parseKey($model, $key, $value) {
// $operatorMatch = '/^(((' . implode(')|(', $this->_sqlOps);
// $operatorMatch .= ')\\x20?)|<[>=]?(?![^>]+>)\\x20?|[>=!]{1,3}(?!<)\\x20?)/is';
// $bound = (strpos($key, '?') !== false || (is_array($value) && strpos($key, ':') !== false));
//
// if (strpos($key, ' ') === false) {
// $operator = 'LIKE';
// } else {
// list($key, $operator) = explode(' ', trim($key), 2);
//
// if (!preg_match($operatorMatch, trim($operator)) && strpos($operator, ' ') !== false) {
// $key = $key . ' ' . $operator;
// $split = strrpos($key, ' ');
// $operator = substr($key, $split);
// $key = substr($key, 0, $split);
// }
// }
//
// $virtual = false;
// if (is_object($model) && $model->isVirtualField($key)) {
// $key = $this->_quoteFields($model->getVirtualField($key));
// $virtual = true;
// }
//
// $type = is_object($model) ? $model->getColumnType($key) : null;
// $null = $value === null || (is_array($value) && empty($value));
//
// if (strtolower($operator) === 'not') {
// $data = $this->conditionKeysToString(
// array($operator => array($key => $value)), true, $model
// );
// return $data[0];
// }
//
// $value = $this->value($value, $type);
//
// if (!$virtual && $key !== '?') {
// $isKey = (strpos($key, '(') !== false || strpos($key, ')') !== false);
// $key = $isKey ? $this->_quoteFields($key) : $this->name($key);
// }
//
// if ($bound) {
// return String::insert($key . ' ' . trim($operator), $value);
// }
//
// if (!preg_match($operatorMatch, trim($operator))) {
// $operator .= ' LIKE';
// }
// $operator = trim($operator);
//
// if (is_array($value)) {
// $value = implode(', ', $value);
//
// switch ($operator) {
// case '=':
// $operator = 'IN';
// break;
// case '!=':
// case '<>':
// $operator = 'NOT IN';
// break;
// }
// $value = "({$value})";
// } elseif ($null || $value === 'NULL') {
// switch ($operator) {
// case '=':
// $operator = 'IS';
// break;
// case '!=':
// case '<>':
// $operator = 'IS NOT';
// break;
// }
// }
// if ($virtual) {
// return "({$key}) {$operator} {$value}";
// }
// return "{$key} {$operator} {$value}";
// }
}
@sebastienbarre
Copy link

Interesting. Is this for CakePHP 1.3 or 2.x? Thanks

@ceeram
Copy link
Author

ceeram commented Apr 8, 2013

@sebastienbarre This is for 2.x you might want to take a took at https://gist.github.com/shrimpwagon/5207702 though as my version is customized for thoroughbred

@jacksnodgrass
Copy link

a DATABASE_CONFIG example would be nice to have in the comments at the top of the code page... I am unclear on how this code is suppsed to get:
$this->_connection = new PDO("odbc:{$this->config['driver']}", null, null, $flags);
filled out correctly... I had to put in:
$this->_connection = new PDO("odbc:MSSQLServer", $this->config['login'], $this->config['password'], $flags);
to get the initial connection... have not progressed past that yet... but I do get my odbc mssql connection. that's a start.

@helpse
Copy link

helpse commented Nov 13, 2013

@ceeram, with some minor changes, this works perfect for my cake 2.3 app.
It's selecting ok, but i'm having problems on inserting/updating. I might use 2.x Model instead of my 2.3.
Which 2.x was this script made for?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment