Skip to content

Commit

Permalink
Implement Protocol v7 and Timezone (#53)
Browse files Browse the repository at this point in the history
* update protocol to v7

* add timezone to CASConstants

* add parsing about timezone

* update testcase for PROTOCOL_V7

* improve testcase for calling createConnection as default

* implement timezone for PROTOCOL_V7

* add test code for Timezone

* add 'moment-timezone' because DST issue
  • Loading branch information
hun-a authored and kisoo-han committed Jun 26, 2017
1 parent 6fe0e95 commit 4c6e4b4
Show file tree
Hide file tree
Showing 10 changed files with 938 additions and 20 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@
"eslint": "3.6.1",
"istanbul": "0.4.5",
"mocha": "3.0.2"
},
"dependencies": {
"moment-timezone": "^0.5.13"
}
}
31 changes: 26 additions & 5 deletions src/constants/CASConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,11 @@ exports.CUBRIDDataType = {
CCI_U_TYPE_DATETIME : 22,
CCI_U_TYPE_BLOB : 23,
CCI_U_TYPE_CLOB : 24,
CCI_U_TYPE_ENUM : 25
CCI_U_TYPE_ENUM : 25,
CCI_U_TYPE_TIMESTAMPTZ : 29,
CCI_U_TYPE_TIMESTAMPLTZ : 30,
CCI_U_TYPE_DATETIMETZ : 31,
CCI_U_TYPE_DATETIMELTZ : 32
};

/**
Expand Down Expand Up @@ -246,6 +250,14 @@ exports.getCUBRIDDataTypeName = function (type) {
return 'ENUM';
case this.CUBRIDDataType.CCI_U_TYPE_RESULTSET:
return 'Resultset';
case this.CUBRIDDataType.CCI_U_TYPE_TIMESTAMPTZ:
return 'TimestampTz';
case this.CUBRIDDataType.CCI_U_TYPE_TIMESTAMPLTZ:
return 'TimestampLtz';
case this.CUBRIDDataType.CCI_U_TYPE_DATETIMETZ:
return 'DateTimeTz';
case this.CUBRIDDataType.CCI_U_TYPE_DATETIMELTZ:
return 'DateTimeLtz';
default:
return 'UNKNOWN';
}
Expand Down Expand Up @@ -307,6 +319,14 @@ exports.getCUBRIDDataTypeNumber = function (type) {
return this.CUBRIDDataType.CCI_U_TYPE_CLOB;
case 'resultset':
return this.CUBRIDDataType.CCI_U_TYPE_RESULTSET;
case 'timestamptz':
return this.CUBRIDDataType.CCI_U_TYPE_TIMESTAMPTZ;
case 'timestampltz':
return this.CUBRIDDataType.CCI_U_TYPE_TIMESTAMPLTZ;
case 'datetimetz':
return this.CUBRIDDataType.CCI_U_TYPE_DATETIMETZ;
case 'datetimeltz':
return this.CUBRIDDataType.CCI_U_TYPE_DATETIMELTZ;
default:
return this.CUBRIDDataType.CCI_U_TYPE_UNKNOWN;
}
Expand Down Expand Up @@ -472,9 +492,10 @@ exports.CAS_PROTOCOL_VERSION = (function getProtocolVersion() {
// const CAS_PROTOCOL_VERSION_3 = /* since 8.4.3 */3;
// const CAS_PROTOCOL_VERSION_4 = /* since 9.1.0 */4;
// const CAS_PROTOCOL_VERSION_5 = /* since 9.2.0 */5;
const CAS_PROTOCOL_VERSION_6 = /* since 9.2.26 */6;
// const CAS_PROTOCOL_VERSION_6 = /* since 9.2.26 */6;
const CAS_PROTOCOL_VERSION_7 = /* since 10.0.0 */7;

return CAS_PROTOCOL_VERSION_6;
return CAS_PROTOCOL_VERSION_7;
})();

exports.CAS_VERSION = CAS_PROTO_INDICATOR | exports.CAS_PROTOCOL_VERSION;
Expand All @@ -485,8 +506,8 @@ exports.CAS_CLIENT_JDBC = 3;
exports.CAS_MAGIC_STRING = 'CUBRK';

exports.getProtocolVersion = function (version) {
// At this moment `node-cubrid` supports at most protocol version 6.
for (let protocolVersion = 0; protocolVersion < 7; ++protocolVersion) {
// At this moment `node-cubrid` supports at most protocol version 7.
for (let protocolVersion = 0; protocolVersion < 8; ++protocolVersion) {
if ((CAS_PROTO_INDICATOR | protocolVersion) === version) {
return protocolVersion;
}
Expand Down
16 changes: 15 additions & 1 deletion src/packets/ExecuteQueryPacket.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,16 @@ ExecuteQueryPacket.prototype.parse = function (parser) {
let i;
let info;
let len;
const MASK_TYPE_HAS_2_BYTES = 0x80;

for (i = 0; i < this.columnCount; i++) {
info = new ColumnMetaData();
info.ColumnType = parser._parseByte(); // Column type
let legacyType = parser._parseByte(); // Column type before PROTOCOL_V7
if ((legacyType & MASK_TYPE_HAS_2_BYTES) === 128) {
info.ColumnType = parser._parseByte(); // Column type
} else {
info.ColumnType = legacyType;
}
info.scale = parser._parseShort(); // Scale
info.precision = parser._parseInt(); // Precision
len = parser._parseInt();
Expand Down Expand Up @@ -312,6 +318,14 @@ function _readValue(parser, type, size) {
case CAS.CUBRIDDataType.CCI_U_TYPE_TIMESTAMP:
return parser._parseTimeStamp();

case CAS.CUBRIDDataType.CCI_U_TYPE_TIMESTAMPTZ:
case CAS.CUBRIDDataType.CCI_U_TYPE_TIMESTAMPLTZ:
return parser._parseTimeStampTz(size);

case CAS.CUBRIDDataType.CCI_U_TYPE_DATETIMETZ:
case CAS.CUBRIDDataType.CCI_U_TYPE_DATETIMELTZ:
return parser._parseDateTimeTz(size);

case CAS.CUBRIDDataType.CCI_U_TYPE_OBJECT:
return parser._parseObject();

Expand Down
8 changes: 7 additions & 1 deletion src/packets/GetSchemaPacket.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,17 @@ GetSchemaPacket.prototype.parseRequestSchema = function (parser) {
const numColInfo = parser._parseInt(); // Number of columns

this.infoArray = [];
const MASK_TYPE_HAS_2_BYTES = 0x80;

for (let i = 0; i < numColInfo; ++i) {
let info = new ColumnMetaData();

info.ColumnType = parser._parseByte(); // Column data type
let legacyType = parser._parseByte(); // Column data type
if ((legacyType & MASK_TYPE_HAS_2_BYTES) === 128) {
info.ColumnType = parser._parseByte(); // Column data type
} else {
info.ColumnType = legacyType;
}
info.scale = parser._parseShort(); // Scale
info.precision = parser._parseInt(); // Precision

Expand Down
47 changes: 47 additions & 0 deletions src/packets/PacketReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const CAS = require('../constants/CASConstants');
const DATA_TYPES = require('../constants/DataTypes');
const ErrorMessages = require('../constants/ErrorMessages');
const Timezone = require('../utils/Timezone');

module.exports = PacketReader;

Expand Down Expand Up @@ -236,6 +237,52 @@ PacketReader.prototype._parseTimeStamp = function () {
return date;
};

/**
* Returns a timestamptz value from the internal buffer
* @param size
* @returns {Timezone}
*/
PacketReader.prototype._parseTimeStampTz = function(size) {
const tmp_position = this._offset;
const timestamp = this._parseTimeStamp.call(this);
const timestamp_size = this._offset - tmp_position;
let timezone;

if (timestamp_size > 0) {
const timezoneLength = size - timestamp_size - 1;
timezone = this._buffer.slice(this._offset, this._offset + timezoneLength);
this._offset += size - timestamp_size;
} else {
timezone = '';
this._offset++;
}

return new Timezone(timestamp, timezone);
};

/**
* Returns a datetimetz value from the internal buffer
* @param size
* @returns {Timezone}
*/
PacketReader.prototype._parseDateTimeTz = function(size) {
const tmp_position = this._offset;
const datetime = this._parseDateTime.call(this);
const datetime_size = this._offset - tmp_position;
let timezone;

if (datetime_size > 0) {
const timezoneLength = size - datetime_size - 1;
timezone = this._buffer.slice(this._offset, this._offset + timezoneLength);
this._offset += size - datetime_size;
} else {
timezone = '';
this._offset++;
}

return new Timezone(datetime, timezone);
};

/**
* Returns a char value from the internal buffer
* @return {String}
Expand Down
8 changes: 7 additions & 1 deletion src/packets/PrepareExecutePacket.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,17 @@ PrepareExecutePacket.prototype.parsePrepare = function (parser) {
this.isUpdatable = parser._parseByte() === 1; // Updatable?
this.columnCount = parser._parseInt(); // Column count
this.infoArray = [];
const MASK_TYPE_HAS_2_BYTES = 0x80;

for (let i = 0; i < this.columnCount; ++i) {
let info = new ColumnMetaData();

info.ColumnType = parser._parseByte(); // Column type
let legacyType = parser._parseByte(); // Column type
if ((legacyType & MASK_TYPE_HAS_2_BYTES) === 128) {
info.ColumnType = parser._parseByte();
} else {
info.ColumnType = legacyType;
}
info.scale = parser._parseShort(); // Scale
info.precision = parser._parseInt(); // Precision

Expand Down
182 changes: 182 additions & 0 deletions src/utils/Timezone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
'use strict';
const moment = require('moment-timezone');

module.exports = Timezone;

/**
* Constructor
* @param datetime
* @param timezone
*/
function Timezone(datetime, timezone) {
this.datetime = datetime;

if (timezone) {
this.timezone = timezone.toString();
const tz = this.timezone.split(' ');
this.region = tz[0];

if (!tz[1]) {
// create from moment
this.tzd = moment.tz(this.datetime, this.region).format('z')
} else {
this.tzd = tz[1];
}

const timeOffset = moment.tz(this.datetime, this.region).format('Z').split(':');
this.tzh = timeOffset[0];
this.tzm = timeOffset[1];
} else {
this.timezone = '';
this.tzd = '';
this.tzh = '';
this.tzm = '';
}
}

/**
* Returns 'YYYY-MM-DD HH24:MI:SS TIMEZONE' format
* @returns {string}
*/
Timezone.prototype.toString = function() {
return `${this.format('YYYY-MM-DD HH24:MI:SS.FF')}${(this.timezone ? ` ${this.timezone}` : ``)}`;
};

/**
* This format based Date/Time format from http://www.cubrid.org/manual/10_0/en/sql/function/typecast_fn.html#to-char-date-time
* @param format
* @returns {String}
*/
Timezone.prototype.format = function(format) {
if (!this.datetime) return '';

const weeks = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'];
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'];
const date = this.datetime;
const that = this;

let am = (/AM|A\.M\./i).exec(format);
let pm = (/PM|P\.M\./i).exec(format);
if (am) {
am = am[0];
const code = am.charCodeAt(0) + 15;
pm = am.replace(/A/i, String.fromCharCode(code));
} else if (pm) {
pm = pm[0];
const code = pm.charCodeAt(0) - 15;
am = pm.replace(/P/i, String.fromCharCode(code));
}

format = format.toUpperCase();
const formatRex = /(CC|YYYY|YY|Q|MM|MONTH|MON|DD|DAY|DY|D|d|AM|PM|A\.M\.|P\.M\.|HH12|HH24|HH|MI|SS|FF|TZD|TZH|TZM)/g;

return format.replace(formatRex, function($1) {
switch($1) {
case 'CC':
return Math.floor(parseInt(date.getUTCFullYear() / 100)) + 1;
case 'YYYY':
return date.getUTCFullYear();
case 'YY':
return _zeroPadding(date.getUTCFullYear() % 100);
case 'Q': {
const mon = date.getMonth();
if (mon < 3) return 1;
else if (mon < 6) return 2;
else if (mon < 9) return 3;
else return 4;
} case 'MM':
return _zeroPadding(date.getMonth() + 1);
case 'MONTH':
return months[date.getMonth()];
case 'MON':
return months[date.getMonth()].substr(0, 3);
case 'DD':
return _zeroPadding(date.getUTCDate());
case 'DAY':
return weeks[date.getUTCDay()];
case 'DY':
return weeks[date.getUTCDay()].substr(0, 3);
case 'D':
case 'd':
return (date.getUTCDay() == 0) ? 7 : date.getUTCDay();
case 'AM':
case 'A.M.':
case 'PM':
case 'P.M.':
return (date.getUTCHours() < 12) ? am : pm;
case 'HH':
case 'HH12': {
const hour = date.getUTCHours() % 12;
return _zeroPadding((hour) ? hour : date.getUTCHours());
} case 'HH24':
return _zeroPadding(date.getUTCHours());
case 'MI':
return _zeroPadding(date.getUTCMinutes());
case 'SS':
return _zeroPadding(date.getUTCSeconds());
case 'FF':
return _zeroPadding(date.getMilliseconds(), true);
case 'TZD':
return that.tzd;
case 'TZH':
return that.tzh;
case 'TZM':
return that.tzm;
default:
return $1;
}
});
};

/**
* Return the number of milliseconds between 1 January 1970 00:00:00 UTC and Timezone.datetime
* @returns {Number}
*/
Timezone.prototype.valueOf = function() {
return Date.prototype.valueOf.call(this.datetime);
};

/**
* Return the number of offset for this timezone
* @returns {Number}
*/
Timezone.prototype.getOffset = function() {
const zone = moment.tz.zone(this.region);
return zone.parse(this.datetime) * 60 * 1000;
};

function _zeroPadding(value, isMs) {
let data;
if (value < 10) {
data = `0${value}`;
} else {
data = value;
}

if (isMs) {
if (value < 100) {
data = `0${data}`
}
}

return data;
}
Loading

0 comments on commit 4c6e4b4

Please sign in to comment.