rivet/packages/dio/formatters.tcl (235 lines of code) (raw):
# formatters.tcl -- connector for tdbc, the Tcl database abstraction layer
#
# Copyright 2024 The Apache Software Foundation
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
namespace eval ::DIO::formatters {
# ::itcl::class FieldFormatter
#
# we devolve the role of special field formatter to this
# class. By design this is more sensible with respect to
# the current approach of having such method in subclasses
# because it allows to reuse the functionality of this
# class in other DBMS connector.
# this class must be subclassed for each dbms or SQL dialect
::itcl::class RootFormatter {
private variable special_fields [dict create]
public proc quote {a_string} {
regsub -all {'} $a_string {\'} a_string
return $a_string
}
public method register {table_name field_name ftype} {
dict set special_fields $table_name $field_name $ftype
}
public method build {table_name field_name val convert_to} {
if {[dict exists $special_fields $table_name $field_name]} {
set field_type [dict get $special_fields $table_name $field_name]
if {[catch {
set field_value [$this $field_type $field_name $val $convert_to]
} e einfo]} {
set field_value "'[quote $val]'"
}
return $field_value
} else {
return "'[quote $val]'"
}
}
} ; ## ::itcl::class FieldFormatter
::itcl::class Mysql {
inherit RootFormatter
public method DATE {field_name val convert_to} {
set secs [clock scan $val]
set my_val [clock format $secs -format {%Y-%m-%d}]
return "DATE_FORMAT('$my_val','%Y-%m-%d')"
}
public method DATETIME {field_name val convert_to} {
set secs [clock scan $val]
set my_val [clock format $secs -format {%Y-%m-%d %T}]
return "DATE_FORMAT('$my_val','%Y-%m-%d %T')"
}
public method NOW {field_name val convert_to} {
# we try to be coherent with the original purpose of this method whose
# goal is endow the class with a uniform way to handle timestamps.
# E.g.: Package session expects this case to return a timestamp in seconds
# so that differences with timestamps returned by [clock seconds]
# can be done and session expirations are computed consistently.
# (Bug #53703)
switch $convert_to {
SECS {
if {[::string compare $val "now"] == 0} {
# set secs [clock seconds]
# set my_val [clock format $secs -format {%Y%m%d%H%M%S}]
# return $my_val
return [clock seconds]
} else {
return "UNIX_TIMESTAMP($field_name)"
}
}
default {
if {[::string compare $val, "now"] == 0} {
set secs [clock seconds]
} else {
set secs [clock scan $val]
}
# this is kind of going back and forth from the same
# format,
#set my_val [clock format $secs -format {%Y-%m-%d %T}]
return "FROM_UNIXTIME('$secs')"
}
}
}
public method NULL {field_name val convert_to} {
if {[::string toupper $val] == "NULL"} {
return $val
} else {
return "'[quote $val]'"
}
}
}
::itcl::class Sqlite {
inherit RootFormatter
#
# quote - given a string, return the same string with any single
# quote characters preceded by a backslash
#
method quote {a_string} {
regsub -all {'} $a_string {''} a_string
return $a_string
}
public method DATE {field_name val convert_to} {
set secs [clock scan $val]
set my_val [clock format $secs -format {%Y-%m-%d}]
return "date('$my_val')"
}
public method DATETIME {field_name val convert_to} {
set secs [clock scan $val]
set my_val [clock format $secs -format {%Y-%m-%d %T}]
return "datetime('$my_val')"
}
public method NOW {field_name val convert_to} {
switch $convert_to {
# we try to be coherent with the original purpose of this method whose
# goal is to provide a uniform way to handle timestamps.
# E.g.: Package session expects this case to return a timestamp in seconds
# so that differences with timestamps returned by [clock seconds]
# can be done and session expirations are computed consistently.
# (Bug #53703)
SECS {
if {[::string compare $val "now"] == 0} {
# set secs [clock seconds]
# set my_val [clock format $secs -format "%Y%m%d%H%M%S"]
return [clock seconds]
} else {
# the numbers of seconds must be returned as 'utc' to
# be compared with values returned by [clock seconds]
return "strftime('%s',$field_name,'utc')"
}
}
default {
if {[::string compare $val, "now"] == 0} {
set secs [clock seconds]
} else {
set secs [clock scan $val]
}
set my_val [clock format $secs -format {%Y-%m-%d %T}]
return "datetime('$my_val')"
}
}
}
}
::itcl::class Postgresql {
inherit RootFormatter
public method DATE {field_name val convert_to} {
set secs [clock scan $val]
set my_val [clock format $secs -format {%Y-%m-%d}]
return "'$my_val'"
}
public method DATETIME {field_name val convert_to} {
set secs [clock scan $val]
set my_val [clock format $secs -format {%Y-%m-%d %T}]
return "'$my_val'"
}
public method NOW {field_name val convert_to} {
switch $convert_to {
# we try to be coherent with the original purpose of this method whose
# goal is to provide a uniform way to handle timestamps.
# E.g.: Package session expects this case to return a timestamp in seconds
# so that differences with timestamps returned by [clock seconds]
# can be done and session expirations are computed consistently.
# (Bug #53703)
SECS {
if {[::string compare $val "now"] == 0} {
# set secs [clock seconds]
# set my_val [clock format $secs -format {%Y%m%d%H%M%S}]
# return $my_val
return [clock seconds]
} else {
return "extract(epoch from $field_name)"
}
}
default {
if {[::string compare $val, "now"] == 0} {
set secs [clock seconds]
} else {
set secs [clock scan $val]
}
# this is kind of going back and forth from the same
# format,
return "'[clock format $secs -format {%Y-%m-%d %T}]'"
}
}
}
}
::itcl::class Oracle {
inherit RootFormatter
public method DATE {field_name val convert_to} {
set secs [clock scan $val]
set my_val [clock format $secs -format {%Y-%m-%d}]
return "to_date('$my_val','YYYY-MM-DD')"
}
public method DATETIME {field_name val convert_to} {
set secs [clock scan $val]
set my_val [clock format $secs -format {%Y-%m-%d %T}]
return "to_date('$my_val','YYYY-MM-DD HH24:MI:SS')"
}
public method NOW {field_name val convert_to} {
switch $convert_to {
SECS {
if {[::string compare $val "now"] == 0} {
set secs [clock seconds]
set my_val [clock format $secs -format {%Y%m%d%H%M%S}]
return $my_val
} else {
return "($field_name - to_date('1970-01-01')) * 86400"
#return "to_char($field_name, 'YYYYMMDDHH24MISS')"
}
}
default {
if {[::string compare $val "now"] == 0} {
set secs [clock seconds]
} else {
set secs [clock scan $val]
}
set my_val [clock format $secs -format {%Y-%m-%d %T}]
return "to_date('$my_val', 'YYYY-MM-DD HH24:MI:SS')"
}
}
}
}
}; ## namespace eval DIO
package provide dio::formatters 1.0