diff --git a/README b/README index 0d1a99c..06383b4 100644 --- a/README +++ b/README @@ -32,7 +32,7 @@ go-pgsql is currently missing support for some features, including: - authentication types other than MD5 - SSL encrypted sessions -- some data types like date/time, bytea, ... +- some data types like bytea, ... - canceling commands/queries - bulk copy ... diff --git a/conn.go b/conn.go index 296331f..537cee7 100644 --- a/conn.go +++ b/conn.go @@ -71,6 +71,7 @@ type Conn struct { backendPID int32 backendSecretKey int32 onErrorDontRequireReadyForQuery bool + runtimeParameters map[string]string } // Connect establishes a database connection. @@ -95,7 +96,7 @@ func Connect(parameters *ConnParams) (conn *Conn, err os.Error) { newConn.params = params if params.Host == "" { - params.Host = "127.0.0.1" + params.Host = "localhost" } if params.Port == 0 { params.Port = 5432 @@ -116,6 +117,8 @@ func Connect(parameters *ConnParams) (conn *Conn, err os.Error) { newConn.reader = bufio.NewReader(tcpConn) newConn.writer = bufio.NewWriter(tcpConn) + newConn.runtimeParameters = make(map[string]string) + newConn.writeStartup() newConn.readBackendMessages(nil) @@ -231,6 +234,17 @@ func (conn *Conn) Query(command string) (res *ResultSet, err os.Error) { return } +// RuntimeParameter returns the value of the specified runtime parameter. +// If the value was successfully retrieved, ok is true, otherwise false. +func (conn *Conn) RuntimeParameter(name string) (value string, ok bool) { + if conn.LogLevel >= LogVerbose { + defer conn.logExit(conn.logEnter("*Conn.RuntimeParameter")) + } + + value, ok = conn.runtimeParameters[name] + return +} + // Scan executes the command and scans the fields of the first row // in the ResultSet, trying to store field values into the specified // arguments. The arguments must be of pointer types. If a row has diff --git a/conn_log.go b/conn_log.go index d591720..d111d07 100644 --- a/conn_log.go +++ b/conn_log.go @@ -27,12 +27,12 @@ func (conn *Conn) logError(level LogLevel, err os.Error) { } func (conn *Conn) logEnter(funcName string) string { - conn.log(LogDebug, "entering: ", "pgsqconn."+funcName) + conn.log(LogDebug, "entering: ", "pgsql."+funcName) return funcName } func (conn *Conn) logExit(funcName string) { - conn.log(LogDebug, "exiting: ", "pgsqconn."+funcName) + conn.log(LogDebug, "exiting: ", "pgsql."+funcName) } func (conn *Conn) logAndConvertPanic(x interface{}) (err os.Error) { diff --git a/conn_read.go b/conn_read.go index 48d6781..3b85cc0 100644 --- a/conn_read.go +++ b/conn_read.go @@ -59,68 +59,6 @@ func (conn *Conn) readString() string { return string(b[0 : len(b)-1]) } -func (conn *Conn) readDataRow(res *ResultSet) { - // Just eat message length. - conn.readInt32() - - fieldCount := conn.readInt16() - - var ord int16 - for ord = 0; ord < fieldCount; ord++ { - valLen := conn.readInt32() - - var val []byte - - if valLen == -1 { - val = nil - } else { - val = make([]byte, valLen) - conn.read(val) - } - - res.values[ord] = val - } -} - -func (conn *Conn) readRowDescription(res *ResultSet) { - // Just eat message length. - conn.readInt32() - - fieldCount := conn.readInt16() - - res.fields = make([]field, fieldCount) - res.values = make([][]byte, fieldCount) - - var ord int16 - for ord = 0; ord < fieldCount; ord++ { - res.fields[ord].name = conn.readString() - - // Just eat table OID. - conn.readInt32() - - // Just eat field OID. - conn.readInt16() - - // Just eat field data type OID. - conn.readInt32() - - // Just eat field size. - conn.readInt16() - - // Just eat field type modifier. - conn.readInt32() - - format := fieldFormat(conn.readInt16()) - switch format { - case textFormat: - case binaryFormat: - default: - panic("unsupported field format") - } - res.fields[ord].format = format - } -} - func (conn *Conn) readAuthenticationRequest() { if conn.LogLevel >= LogDebug { defer conn.logExit(conn.logEnter("*Conn.readAuthenticationRequest")) @@ -242,6 +180,29 @@ func (conn *Conn) readCommandComplete(res *ResultSet) { } } +func (conn *Conn) readDataRow(res *ResultSet) { + // Just eat message length. + conn.readInt32() + + fieldCount := conn.readInt16() + + var ord int16 + for ord = 0; ord < fieldCount; ord++ { + valLen := conn.readInt32() + + var val []byte + + if valLen == -1 { + val = nil + } else { + val = make([]byte, valLen) + conn.read(val) + } + + res.values[ord] = val + } +} + func (conn *Conn) readEmptyQueryResponse() { if conn.LogLevel >= LogDebug { defer conn.logExit(conn.logEnter("*Conn.readEmptyQueryResponse")) @@ -323,6 +284,15 @@ func (conn *Conn) readErrorOrNoticeResponse(isError bool) { } } +func (conn *Conn) readNoData() { + if conn.LogLevel >= LogDebug { + defer conn.logExit(conn.logEnter("*Conn.readNoData")) + } + + // Just eat message length. + conn.readInt32() +} + func (conn *Conn) readParameterStatus() { if conn.LogLevel >= LogDebug { defer conn.logExit(conn.logEnter("*Conn.readParameterStatus")) @@ -337,6 +307,8 @@ func (conn *Conn) readParameterStatus() { if conn.LogLevel >= LogDebug { conn.logf(LogDebug, "ParameterStatus: Name: '%s', Value: '%s'", name, value) } + + conn.runtimeParameters[name] = value } func (conn *Conn) readParseComplete() { @@ -381,6 +353,44 @@ func (conn *Conn) readReadyForQuery(res *ResultSet) { conn.state = readyState{} } +func (conn *Conn) readRowDescription(res *ResultSet) { + // Just eat message length. + conn.readInt32() + + fieldCount := conn.readInt16() + + res.fields = make([]field, fieldCount) + res.values = make([][]byte, fieldCount) + + var ord int16 + for ord = 0; ord < fieldCount; ord++ { + res.fields[ord].name = conn.readString() + + // Just eat table OID. + conn.readInt32() + + // Just eat field OID. + conn.readInt16() + + res.fields[ord].typeOID = conn.readInt32() + + // Just eat field size. + conn.readInt16() + + // Just eat field type modifier. + conn.readInt32() + + format := fieldFormat(conn.readInt16()) + switch format { + case textFormat: + case binaryFormat: + default: + panic("unsupported field format") + } + res.fields[ord].format = format + } +} + func (conn *Conn) readBackendMessages(res *ResultSet) { if conn.LogLevel >= LogDebug { defer conn.logExit(conn.logEnter("*Conn.readBackendMessages")) @@ -421,6 +431,10 @@ func (conn *Conn) readBackendMessages(res *ResultSet) { case _ErrorResponse: conn.readErrorOrNoticeResponse(true) + case _NoData: + conn.readNoData() + return + case _NoticeResponse: conn.readErrorOrNoticeResponse(false) diff --git a/conn_write.go b/conn_write.go index f510565..9e2c31f 100644 --- a/conn_write.go +++ b/conn_write.go @@ -9,6 +9,7 @@ import ( "fmt" "math" "strconv" + "time" ) func (conn *Conn) flush() { @@ -134,13 +135,40 @@ func (conn *Conn) writeBind(stmt *Statement) { values[i] = strconv.Itoa(int(val)) case int64: - values[i] = strconv.Itoa64(val) + switch param.typ { + case Date: + values[i] = time.SecondsToUTC(val).Format("2006-01-02") + + case Time, TimeTZ: + values[i] = time.SecondsToUTC(val).Format("15:04:05") + + case Timestamp, TimestampTZ: + values[i] = time.SecondsToUTC(val).Format("2006-01-02 15:04:05") + + default: + values[i] = strconv.Itoa64(val) + } case nil: case string: values[i] = val + case *time.Time: + switch param.typ { + case Date: + values[i] = val.Format("2006-01-02") + + case Time, TimeTZ: + values[i] = val.Format("15:04:05") + + case Timestamp, TimestampTZ: + values[i] = val.Format("2006-01-02 15:04:05") + + default: + panic("invalid use of *time.Time") + } + default: panic("unsupported parameter type") } @@ -212,6 +240,12 @@ func (conn *Conn) writeExecute(stmt *Statement) { } func (conn *Conn) writeParse(stmt *Statement) { + if conn.LogLevel >= LogDebug { + defer conn.logExit(conn.logEnter("*Conn.writeParse")) + + conn.log(LogDebug, fmt.Sprintf("stmt.ActualCommand: '%s'", stmt.ActualCommand())) + } + msgLen := int32(4 + len(stmt.name) + 1 + len(stmt.actualCommand) + 1 + @@ -245,6 +279,12 @@ func (conn *Conn) writePasswordMessage(password string) { } func (conn *Conn) writeQuery(command string) { + if conn.LogLevel >= LogDebug { + defer conn.logExit(conn.logEnter("*Conn.writeQuery")) + + conn.log(LogDebug, fmt.Sprintf("command: '%s'", command)) + } + conn.writeFrontendMessageCode(_Query) conn.writeInt32(int32(4 + len(command) + 1)) conn.writeString0(command) diff --git a/examples/multipleselects.go b/examples/multipleselects.go index af862ee..b45714b 100644 --- a/examples/multipleselects.go +++ b/examples/multipleselects.go @@ -14,7 +14,6 @@ func main() { pgsql.DefaultLogLevel = pgsql.LogError params := &pgsql.ConnParams{ - Host: "127.0.0.1", Database: "testdatabase", User: "testuser", Password: "testpassword", diff --git a/examples/scan.go b/examples/scan.go index 4ee3102..20112b0 100644 --- a/examples/scan.go +++ b/examples/scan.go @@ -20,7 +20,6 @@ func main() { pgsql.DefaultLogLevel = pgsql.LogError params := &pgsql.ConnParams{ - Host: "127.0.0.1", Database: "testdatabase", User: "testuser", Password: "testpassword", diff --git a/examples/statements.go b/examples/statements.go index 22d500c..bf1e40b 100644 --- a/examples/statements.go +++ b/examples/statements.go @@ -43,7 +43,6 @@ func main() { pgsql.DefaultLogLevel = pgsql.LogError params := &pgsql.ConnParams{ - Host: "127.0.0.1", Database: "testdatabase", User: "testuser", Password: "testpassword", diff --git a/parameter.go b/parameter.go index 84f51ee..44f21bc 100644 --- a/parameter.go +++ b/parameter.go @@ -7,6 +7,7 @@ package pgsql import ( "fmt" "os" + "time" ) // Parameter is used to set the value of a parameter in a Statement. @@ -38,8 +39,8 @@ func (p *Parameter) Value() interface{} { } func (p *Parameter) panicInvalidValue(v interface{}) { - panic(fmt.Sprintf("Parameter %s: Invalid value for PostgreSQL type %s: '%v' (Go type: %T)", - p.name, p.typ, v, v)) + panic(os.NewError(fmt.Sprintf("Parameter %s: Invalid value for PostgreSQL type %s: '%v' (Go type: %T)", + p.name, p.typ, v, v))) } // SetValue sets the current value of the Parameter. @@ -51,13 +52,27 @@ func (p *Parameter) SetValue(v interface{}) (err os.Error) { defer func() { if x := recover(); x != nil { if p.stmt == nil { - err = x.(os.Error) + switch ex := x.(type) { + case os.Error: + err = ex + + case string: + err = os.NewError(ex) + + default: + err = os.NewError("pgsql.*Parameter.SetValue: D'oh!") + } } else { err = p.stmt.conn.logAndConvertPanic(x) } } }() + if v == nil { + p.value = nil + return + } + switch p.typ { case Bigint: switch val := v.(type) { @@ -91,6 +106,20 @@ func (p *Parameter) SetValue(v interface{}) (err os.Error) { } p.value = val + case Date, Time, TimeTZ, Timestamp, TimestampTZ: + switch val := v.(type) { + case int64: + p.value = val + + case *time.Time: + t := new(time.Time) + *t = *val + p.value = t + + default: + p.panicInvalidValue(v) + } + case Double: switch val := v.(type) { case float: diff --git a/pgsql_test.go b/pgsql_test.go index 9d906c4..b52d339 100644 --- a/pgsql_test.go +++ b/pgsql_test.go @@ -5,13 +5,15 @@ package pgsql import ( + "fmt" "math" + "strings" "testing" + "time" ) func validParams() *ConnParams { return &ConnParams{ - Host: "127.0.0.1", Database: "testdatabase", User: "testuser", Password: "testpassword", @@ -84,6 +86,16 @@ func withStatementResultSet(t *testing.T, command string, params []*Parameter, f }) } +func param(name string, typ Type, value interface{}) *Parameter { + p := NewParameter(name, typ) + err := p.SetValue(value) + if err != nil { + panic(err) + } + + return p +} + func Test_Connect_NilParams_ExpectErrNotNil(t *testing.T) { _, err := Connect(nil) if err == nil { @@ -390,3 +402,205 @@ func Test_Conn_Scan(t *testing.T) { } }) } + +type timeTest struct { + command, timeString string + seconds int64 +} + +func newTimeTest(commandTemplate, format, value string, tz bool) *timeTest { + test := new(timeTest) + + var tzFormatExtra, tzValueExtra string + if tz { + tzFormatExtra = "-0700" + tzValueExtra = "+0200" + } + + t, _ := time.Parse(format+tzFormatExtra, value+tzValueExtra) + t = time.SecondsToUTC(t.Seconds()) + + if strings.Index(commandTemplate, "%s") > -1 { + test.command = fmt.Sprintf(commandTemplate, value) + } else { + test.command = commandTemplate + } + test.seconds = t.Seconds() + test.timeString = t.String() + + return test +} + +const ( + dateFormat = "2006-01-02" + timeFormat = "15:04:05" + timestampFormat = "2006-01-02 15:04:05" +) + +func Test_Conn_Scan_Time(t *testing.T) { + tests := []*timeTest{ + newTimeTest( + "SELECT DATE '%s';", + dateFormat, + "2010-08-14", + false), + newTimeTest( + "SELECT TIME '%s';", + timeFormat, + "18:43:32", + false), + newTimeTest( + "SELECT TIME WITH TIME ZONE '%s';", + timeFormat, + "18:43:32", + true), + newTimeTest( + "SELECT TIMESTAMP '%s';", + timestampFormat, + "2010-08-14 18:43:32", + false), + newTimeTest( + "SELECT TIMESTAMP WITH TIME ZONE '%s';", + timestampFormat, + "2010-08-14 18:43:32", + true), + } + + for _, test := range tests { + withConn(t, func(conn *Conn) { + _, err := conn.Execute("SET TimeZone = +02; SET DateStyle = ISO") + if err != nil { + t.Error("failed to set time zone or date style:", err) + return + } + + var seconds int64 + _, err = conn.Scan(test.command, &seconds) + if err != nil { + t.Error(err) + return + } + if seconds != test.seconds { + t.Errorf("'%s' failed - have: '%d', but want '%d'", test.command, seconds, test.seconds) + } + + var tm *time.Time + _, err = conn.Scan(test.command, &tm) + if err != nil { + t.Error(err) + return + } + timeString := tm.String() + if timeString != test.timeString { + t.Errorf("'%s' failed - have: '%s', but want '%s'", test.command, timeString, test.timeString) + } + }) + } +} + +func Test_Insert_Time(t *testing.T) { + tests := []*timeTest{ + newTimeTest( + "SELECT _d FROM _gopgsql_test_time;", + dateFormat, + "2010-08-14", + false), + newTimeTest( + "SELECT _t FROM _gopgsql_test_time;", + timeFormat, + "20:03:38", + false), + newTimeTest( + "SELECT _ttz FROM _gopgsql_test_time;", + timeFormat, + "20:03:38", + true), + newTimeTest( + "SELECT _ts FROM _gopgsql_test_time;", + timestampFormat, + "2010-08-14 20:03:38", + false), + newTimeTest( + "SELECT _tstz FROM _gopgsql_test_time;", + timestampFormat, + "2010-08-14 20:03:38", + true), + } + + for _, test := range tests { + withConn(t, func(conn *Conn) { + conn.Execute("DROP TABLE _gopgsql_test_time;") + + _, err := conn.Execute( + `CREATE TABLE _gopgsql_test_time + ( + _d DATE, + _t TIME, + _ttz TIME WITH TIME ZONE, + _ts TIMESTAMP, + _tstz TIMESTAMP WITH TIME ZONE + );`) + if err != nil { + t.Error("failed to create table:", err) + return + } + defer func() { + conn.Execute("DROP TABLE _gopgsql_test_time;") + }() + + _, err = conn.Execute("SET TimeZone = +02; SET DateStyle = ISO") + if err != nil { + t.Error("failed to set time zone or date style:", err) + return + } + + _d, _ := time.Parse(dateFormat, "2010-08-14") + _t, _ := time.Parse(timeFormat+"", "20:03:38") + _ttz, _ := time.Parse(timeFormat+"", "20:03:38") + _ts, _ := time.Parse(timestampFormat, "2010-08-14 20:03:38") + _tstz, _ := time.Parse(timestampFormat+"", "2010-08-14 20:03:38") + + stmt, err := conn.Prepare( + `INSERT INTO _gopgsql_test_time + (_d, _t, _ttz, _ts, _tstz) + VALUES + (@d, @t, @ttz, @ts, @tstz);`, + param("@d", Date, _d), + param("@t", Time, _t.Seconds()), + param("@ttz", TimeTZ, _ttz), + param("@ts", Timestamp, _ts), + param("@tstz", TimestampTZ, _tstz.Seconds())) + if err != nil { + t.Error("failed to prepare insert statement:", err) + return + } + defer stmt.Close() + + _, err = stmt.Execute() + if err != nil { + t.Error("failed to execute insert statement:", err) + } + + var seconds int64 + _, err = conn.Scan(test.command, &seconds) + if err != nil { + t.Error(err) + return + } + if seconds != test.seconds { + t.Errorf("'%s' failed - have: '%d', but want '%d'", test.command, seconds, test.seconds) + } + + var tm *time.Time + _, err = conn.Scan(test.command, &tm) + if err != nil { + t.Error(err) + return + } + timeString := tm.String() + if timeString != test.timeString { + t.Errorf("'%s' failed - have: '%s', but want '%s'", test.command, timeString, test.timeString) + } + }) + } +} diff --git a/resultset.go b/resultset.go index 7d601cf..93afaaf 100644 --- a/resultset.go +++ b/resultset.go @@ -9,6 +9,7 @@ import ( "math" "os" "strconv" + "time" ) type fieldFormat int16 @@ -19,8 +20,9 @@ const ( ) type field struct { - name string - format fieldFormat + name string + format fieldFormat + typeOID int32 } // ResultSet reads the results of a query, row by row, and provides methods to @@ -480,6 +482,90 @@ func (res *ResultSet) String(ord int) (value string, isNull bool, err os.Error) return } +// Time returns the value of the field with the specified ordinal as *time.Time. +func (res *ResultSet) Time(ord int) (value *time.Time, isNull bool, err os.Error) { + if res.conn.LogLevel >= LogVerbose { + defer res.conn.logExit(res.conn.logEnter("*ResultSet.Time")) + } + + defer func() { + if x := recover(); x != nil { + err = res.conn.logAndConvertPanic(x) + } + }() + + // We need to convert the parsed *time.Time to seconds and back, + // because otherwise the Weekday field will always equal 0 (Sunday). + // See http://code.google.com/p/go/issues/detail?id=1025 + seconds, isNull, err := res.TimeSeconds(ord) + if err != nil { + return + } + if isNull { + return + } + + value = time.SecondsToUTC(seconds) + + return +} + +// TimeSeconds returns the value of the field with the specified ordinal as int64. +func (res *ResultSet) TimeSeconds(ord int) (value int64, isNull bool, err os.Error) { + if res.conn.LogLevel >= LogVerbose { + defer res.conn.logExit(res.conn.logEnter("*ResultSet.TimeSeconds")) + } + + defer func() { + if x := recover(); x != nil { + err = res.conn.logAndConvertPanic(x) + } + }() + + isNull, err = res.IsNull(ord) + if isNull || err != nil { + return + } + + val := res.values[ord] + + var t *time.Time + + switch res.fields[ord].format { + case textFormat: + var format string + switch res.fields[ord].typeOID { + case _DATEOID: + format = "2006-01-02" + + case _TIMEOID, _TIMETZOID: + format = "15:04:05" + + case _TIMESTAMPOID, _TIMESTAMPTZOID: + format = "2006-01-02 15:04:05" + } + + var tzFormatExtra, tzValueExtra string + switch res.fields[ord].typeOID { + case _TIMETZOID, _TIMESTAMPTZOID: + tzFormatExtra = "-0700" + tzValueExtra = "00" + } + + t, err = time.Parse(format+tzFormatExtra, string(val)+tzValueExtra) + if err != nil { + panic(err) + } + + case binaryFormat: + panic("not implemented") + } + + value = t.Seconds() + + return +} + // Scan scans the fields of the current row in the ResultSet, trying // to store field values into the specified arguments. The arguments // must be of pointer types. @@ -522,10 +608,23 @@ func (res *ResultSet) Scan(args ...interface{}) (err os.Error) { *a, _, err = res.Int32(i) case *int64: - *a, _, err = res.Int64(i) + switch res.fields[i].typeOID { + case _DATEOID, _TIMEOID, _TIMETZOID, _TIMESTAMPOID, _TIMESTAMPTZOID: + *a, _, err = res.TimeSeconds(i) + + default: + *a, _, err = res.Int64(i) + } case *string: *a, _, err = res.String(i) + + case **time.Time: + var t *time.Time + t, _, err = res.Time(i) + if err == nil { + *a = t + } } if err != nil { diff --git a/statement.go b/statement.go index aacbb6c..71bd95b 100644 --- a/statement.go +++ b/statement.go @@ -9,7 +9,6 @@ import ( "fmt" "os" "regexp" - "strings" ) var nextStatementId, nextPortalId uint64 @@ -25,28 +24,55 @@ type Statement struct { name2param map[string]*Parameter } +func replaceParameterNameInSubstring(s, old, new string, buf *bytes.Buffer, paramRegExp *regexp.Regexp) { + matchIndices := paramRegExp.FindStringIndex(s) + prevMatchEnd := 1 + + for i := 0; i < len(matchIndices); i += 2 { + matchStart := matchIndices[i] + matchEnd := matchIndices[i+1] + + buf.WriteString(s[prevMatchEnd-1 : matchStart+1]) + buf.WriteString(new) + + prevMatchEnd = matchEnd + } + + if prevMatchEnd > 1 { + buf.WriteString(s[prevMatchEnd-1:]) + return + } + + buf.WriteString(s) +} + func replaceParameterName(command, old, new string) string { + paramRegExp := regexp.MustCompile("[\\- |\n\r\t,)(;=+/<>][:|@]" + old[1:] + "([\\- |\n\r\t,)(;=+/<>]|$)") + buf := bytes.NewBuffer(nil) quoteIndices := quoteRegExp.FindStringIndex(command) prevQuoteEnd := 0 + for i := 0; i < len(quoteIndices); i += 2 { quoteStart := quoteIndices[i] quoteEnd := quoteIndices[i+1] - buf.WriteString(strings.Replace(command[prevQuoteEnd:quoteStart], old, new, -1)) + replaceParameterNameInSubstring(command[prevQuoteEnd:quoteStart], old, new, buf, paramRegExp) buf.WriteString(command[quoteStart:quoteEnd]) prevQuoteEnd = quoteEnd } if buf.Len() > 0 { - buf.WriteString(strings.Replace(command[prevQuoteEnd:], old, new, -1)) + replaceParameterNameInSubstring(command[prevQuoteEnd:], old, new, buf, paramRegExp) return buf.String() } - return strings.Replace(command, old, new, -1) + replaceParameterNameInSubstring(command, old, new, buf, paramRegExp) + + return buf.String() } func adjustCommand(command string, params []*Parameter) string { diff --git a/types.go b/types.go index ca7abda..c499a2b 100644 --- a/types.go +++ b/types.go @@ -85,15 +85,20 @@ const ( type Type int32 const ( - Boolean Type = _BOOLOID - Char Type = _CHAROID - Real Type = _FLOAT4OID - Double Type = _FLOAT8OID - Smallint Type = _INT2OID - Integer Type = _INT4OID - Bigint Type = _INT8OID - Text Type = _TEXTOID - Varchar Type = _VARCHAROID + Boolean Type = _BOOLOID + Char Type = _CHAROID + Date Type = _DATEOID + Real Type = _FLOAT4OID + Double Type = _FLOAT8OID + Smallint Type = _INT2OID + Integer Type = _INT4OID + Bigint Type = _INT8OID + Text Type = _TEXTOID + Time Type = _TIMEOID + TimeTZ Type = _TIMETZOID + Timestamp Type = _TIMESTAMPOID + TimestampTZ Type = _TIMESTAMPTZOID + Varchar Type = _VARCHAROID ) func (t Type) String() string { @@ -104,6 +109,9 @@ func (t Type) String() string { case Char: return "Char" + case Date: + return "Date" + case Real: return "Real" @@ -122,6 +130,18 @@ func (t Type) String() string { case Text: return "Text" + case Time: + return "Time" + + case TimeTZ: + return "TimeTZ" + + case Timestamp: + return "Timestamp" + + case TimestampTZ: + return "TimestampTZ" + case Varchar: return "Varchar" }