From f2c203382bb8da61081e72bbe45d5fab9b8a8f94 Mon Sep 17 00:00:00 2001 From: dm-2 <45519614+dm-2@users.noreply.github.com> Date: Thu, 7 Jul 2022 13:09:36 +0100 Subject: [PATCH] vendor github.com/openark/golib --- vendor/github.com/openark/golib/LICENSE | 201 +++++++++ vendor/github.com/openark/golib/README.md | 9 + vendor/github.com/openark/golib/go.mod | 3 + vendor/github.com/openark/golib/go.sum | 0 vendor/github.com/openark/golib/log/log.go | 268 +++++++++++ vendor/github.com/openark/golib/math/math.go | 119 +++++ .../openark/golib/sqlutils/dialect.go | 49 ++ .../openark/golib/sqlutils/sqlite_dialect.go | 130 ++++++ .../golib/sqlutils/sqlite_dialect_test.go | 242 ++++++++++ .../openark/golib/sqlutils/sqlutils.go | 427 ++++++++++++++++++ vendor/github.com/openark/golib/tests/spec.go | 76 ++++ vendor/github.com/openark/golib/util/text.go | 103 +++++ .../openark/golib/util/text_test.go | 88 ++++ 13 files changed, 1715 insertions(+) create mode 100644 vendor/github.com/openark/golib/LICENSE create mode 100644 vendor/github.com/openark/golib/README.md create mode 100644 vendor/github.com/openark/golib/go.mod create mode 100644 vendor/github.com/openark/golib/go.sum create mode 100644 vendor/github.com/openark/golib/log/log.go create mode 100644 vendor/github.com/openark/golib/math/math.go create mode 100644 vendor/github.com/openark/golib/sqlutils/dialect.go create mode 100644 vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go create mode 100644 vendor/github.com/openark/golib/sqlutils/sqlite_dialect_test.go create mode 100644 vendor/github.com/openark/golib/sqlutils/sqlutils.go create mode 100644 vendor/github.com/openark/golib/tests/spec.go create mode 100644 vendor/github.com/openark/golib/util/text.go create mode 100644 vendor/github.com/openark/golib/util/text_test.go diff --git a/vendor/github.com/openark/golib/LICENSE b/vendor/github.com/openark/golib/LICENSE new file mode 100644 index 000000000..6875dcac6 --- /dev/null +++ b/vendor/github.com/openark/golib/LICENSE @@ -0,0 +1,201 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "{}" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2014 Outbrain Inc + +Licensed 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. diff --git a/vendor/github.com/openark/golib/README.md b/vendor/github.com/openark/golib/README.md new file mode 100644 index 000000000..ee0907e94 --- /dev/null +++ b/vendor/github.com/openark/golib/README.md @@ -0,0 +1,9 @@ +Common Go libraries + +To import & use: +``` +go get "github.com/openark/golib/math" +go get "github.com/openark/golib/sqlutils" +go get "github.com/openark/golib/tests" +... +``` diff --git a/vendor/github.com/openark/golib/go.mod b/vendor/github.com/openark/golib/go.mod new file mode 100644 index 000000000..d2096e2ff --- /dev/null +++ b/vendor/github.com/openark/golib/go.mod @@ -0,0 +1,3 @@ +module github.com/openark/golib + +go 1.16 diff --git a/vendor/github.com/openark/golib/go.sum b/vendor/github.com/openark/golib/go.sum new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/github.com/openark/golib/log/log.go b/vendor/github.com/openark/golib/log/log.go new file mode 100644 index 000000000..26d33c997 --- /dev/null +++ b/vendor/github.com/openark/golib/log/log.go @@ -0,0 +1,268 @@ +/* + Copyright 2014 Outbrain Inc. + + Licensed 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. +*/ + +package log + +import ( + "errors" + "fmt" + "log/syslog" + "os" + "runtime/debug" + "time" +) + +// LogLevel indicates the severity of a log entry +type LogLevel int + +func (this LogLevel) String() string { + switch this { + case FATAL: + return "FATAL" + case CRITICAL: + return "CRITICAL" + case ERROR: + return "ERROR" + case WARNING: + return "WARNING" + case NOTICE: + return "NOTICE" + case INFO: + return "INFO" + case DEBUG: + return "DEBUG" + } + return "unknown" +} + +func LogLevelFromString(logLevelName string) (LogLevel, error) { + switch logLevelName { + case "FATAL": + return FATAL, nil + case "CRITICAL": + return CRITICAL, nil + case "ERROR": + return ERROR, nil + case "WARNING": + return WARNING, nil + case "NOTICE": + return NOTICE, nil + case "INFO": + return INFO, nil + case "DEBUG": + return DEBUG, nil + } + return 0, fmt.Errorf("Unknown LogLevel name: %+v", logLevelName) +} + +const ( + FATAL LogLevel = iota + CRITICAL + ERROR + WARNING + NOTICE + INFO + DEBUG +) + +const TimeFormat = "2006-01-02 15:04:05" + +// globalLogLevel indicates the global level filter for all logs (only entries with level equals or higher +// than this value will be logged) +var globalLogLevel LogLevel = DEBUG +var printStackTrace bool = false + +// syslogWriter is optional, and defaults to nil (disabled) +var syslogLevel LogLevel = ERROR +var syslogWriter *syslog.Writer + +// SetPrintStackTrace enables/disables dumping the stack upon error logging +func SetPrintStackTrace(shouldPrintStackTrace bool) { + printStackTrace = shouldPrintStackTrace +} + +// SetLevel sets the global log level. Only entries with level equals or higher than +// this value will be logged +func SetLevel(logLevel LogLevel) { + globalLogLevel = logLevel +} + +// GetLevel returns current global log level +func GetLevel() LogLevel { + return globalLogLevel +} + +// EnableSyslogWriter enables, if possible, writes to syslog. These will execute _in addition_ to normal logging +func EnableSyslogWriter(tag string) (err error) { + syslogWriter, err = syslog.New(syslog.LOG_ERR, tag) + if err != nil { + syslogWriter = nil + } + return err +} + +// SetSyslogLevel sets the minimal syslog level. Only entries with level equals or higher than +// this value will be logged. However, this is also capped by the global log level. That is, +// messages with lower level than global-log-level will be discarded at any case. +func SetSyslogLevel(logLevel LogLevel) { + syslogLevel = logLevel +} + +// logFormattedEntry nicely formats and emits a log entry +func logFormattedEntry(logLevel LogLevel, message string, args ...interface{}) string { + if logLevel > globalLogLevel { + return "" + } + // if TZ env variable is set, update the timestamp timezone + localizedTime := time.Now() + tzLocation := os.Getenv("TZ") + if tzLocation != "" { + location, err := time.LoadLocation(tzLocation) + if err == nil { // if invalid tz location was provided, just leave it as the default + localizedTime = time.Now().In(location) + } + } + + msgArgs := fmt.Sprintf(message, args...) + entryString := fmt.Sprintf("%s %s %s", localizedTime.Format(TimeFormat), logLevel, msgArgs) + fmt.Fprintln(os.Stderr, entryString) + + if syslogWriter != nil { + go func() error { + if logLevel > syslogLevel { + return nil + } + switch logLevel { + case FATAL: + return syslogWriter.Emerg(msgArgs) + case CRITICAL: + return syslogWriter.Crit(msgArgs) + case ERROR: + return syslogWriter.Err(msgArgs) + case WARNING: + return syslogWriter.Warning(msgArgs) + case NOTICE: + return syslogWriter.Notice(msgArgs) + case INFO: + return syslogWriter.Info(msgArgs) + case DEBUG: + return syslogWriter.Debug(msgArgs) + } + return nil + }() + } + return entryString +} + +// logEntry emits a formatted log entry +func logEntry(logLevel LogLevel, message string, args ...interface{}) string { + entryString := message + for _, s := range args { + entryString += fmt.Sprintf(" %s", s) + } + return logFormattedEntry(logLevel, entryString) +} + +// logErrorEntry emits a log entry based on given error object +func logErrorEntry(logLevel LogLevel, err error) error { + if err == nil { + // No error + return nil + } + entryString := fmt.Sprintf("%+v", err) + logEntry(logLevel, entryString) + if printStackTrace { + debug.PrintStack() + } + return err +} + +func Debug(message string, args ...interface{}) string { + return logEntry(DEBUG, message, args...) +} + +func Debugf(message string, args ...interface{}) string { + return logFormattedEntry(DEBUG, message, args...) +} + +func Info(message string, args ...interface{}) string { + return logEntry(INFO, message, args...) +} + +func Infof(message string, args ...interface{}) string { + return logFormattedEntry(INFO, message, args...) +} + +func Notice(message string, args ...interface{}) string { + return logEntry(NOTICE, message, args...) +} + +func Noticef(message string, args ...interface{}) string { + return logFormattedEntry(NOTICE, message, args...) +} + +func Warning(message string, args ...interface{}) error { + return errors.New(logEntry(WARNING, message, args...)) +} + +func Warningf(message string, args ...interface{}) error { + return errors.New(logFormattedEntry(WARNING, message, args...)) +} + +func Error(message string, args ...interface{}) error { + return errors.New(logEntry(ERROR, message, args...)) +} + +func Errorf(message string, args ...interface{}) error { + return errors.New(logFormattedEntry(ERROR, message, args...)) +} + +func Errore(err error) error { + return logErrorEntry(ERROR, err) +} + +func Critical(message string, args ...interface{}) error { + return errors.New(logEntry(CRITICAL, message, args...)) +} + +func Criticalf(message string, args ...interface{}) error { + return errors.New(logFormattedEntry(CRITICAL, message, args...)) +} + +func Criticale(err error) error { + return logErrorEntry(CRITICAL, err) +} + +// Fatal emits a FATAL level entry and exists the program +func Fatal(message string, args ...interface{}) error { + logEntry(FATAL, message, args...) + os.Exit(1) + return errors.New(logEntry(CRITICAL, message, args...)) +} + +// Fatalf emits a FATAL level entry and exists the program +func Fatalf(message string, args ...interface{}) error { + logFormattedEntry(FATAL, message, args...) + os.Exit(1) + return errors.New(logFormattedEntry(CRITICAL, message, args...)) +} + +// Fatale emits a FATAL level entry and exists the program +func Fatale(err error) error { + logErrorEntry(FATAL, err) + os.Exit(1) + return err +} diff --git a/vendor/github.com/openark/golib/math/math.go b/vendor/github.com/openark/golib/math/math.go new file mode 100644 index 000000000..f1f2068e4 --- /dev/null +++ b/vendor/github.com/openark/golib/math/math.go @@ -0,0 +1,119 @@ +/* + Copyright 2014 Shlomi Noach. + + Licensed 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. +*/ + +package math + +func MinInt(i1, i2 int) int { + if i1 < i2 { + return i1 + } + return i2 +} + +func MaxInt(i1, i2 int) int { + if i1 > i2 { + return i1 + } + return i2 +} + +func MinInt64(i1, i2 int64) int64 { + if i1 < i2 { + return i1 + } + return i2 +} + +func MaxInt64(i1, i2 int64) int64 { + if i1 > i2 { + return i1 + } + return i2 +} + +func MinUInt(i1, i2 uint) uint { + if i1 < i2 { + return i1 + } + return i2 +} + +func MaxUInt(i1, i2 uint) uint { + if i1 > i2 { + return i1 + } + return i2 +} + +func MinUInt64(i1, i2 uint64) uint64 { + if i1 < i2 { + return i1 + } + return i2 +} + +func MaxUInt64(i1, i2 uint64) uint64 { + if i1 > i2 { + return i1 + } + return i2 +} + +func MinString(i1, i2 string) string { + if i1 < i2 { + return i1 + } + return i2 +} + +func MaxString(i1, i2 string) string { + if i1 > i2 { + return i1 + } + return i2 +} + +// TernaryString acts like a "? :" C-style ternary operator for strings +func TernaryString(condition bool, resTrue string, resFalse string) string { + if condition { + return resTrue + } + return resFalse +} + +// TernaryString acts like a "? :" C-style ternary operator for ints +func TernaryInt(condition bool, resTrue int, resFalse int) int { + if condition { + return resTrue + } + return resFalse +} + +// AbsInt is an ABS function for int type +func AbsInt(i int) int { + if i >= 0 { + return i + } + return -i +} + +// AbsInt64 is an ABS function for int64 type +func AbsInt64(i int64) int64 { + if i >= 0 { + return i + } + return -i +} diff --git a/vendor/github.com/openark/golib/sqlutils/dialect.go b/vendor/github.com/openark/golib/sqlutils/dialect.go new file mode 100644 index 000000000..19cb55d25 --- /dev/null +++ b/vendor/github.com/openark/golib/sqlutils/dialect.go @@ -0,0 +1,49 @@ +/* + Copyright 2017 GitHub Inc. + + Licensed 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. +*/ + +package sqlutils + +import ( + "regexp" + "strings" +) + +type regexpMap struct { + r *regexp.Regexp + replacement string +} + +func (this *regexpMap) process(text string) (result string) { + return this.r.ReplaceAllString(text, this.replacement) +} + +func rmap(regexpExpression string, replacement string) regexpMap { + return regexpMap{ + r: regexp.MustCompile(regexpSpaces(regexpExpression)), + replacement: replacement, + } +} + +func regexpSpaces(statement string) string { + return strings.Replace(statement, " ", `[\s]+`, -1) +} + +func applyConversions(statement string, conversions []regexpMap) string { + for _, rmap := range conversions { + statement = rmap.process(statement) + } + return statement +} diff --git a/vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go b/vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go new file mode 100644 index 000000000..5937aa42a --- /dev/null +++ b/vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go @@ -0,0 +1,130 @@ +/* + Copyright 2017 GitHub Inc. + + Licensed 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. +*/ + +// What's this about? +// This is a brute-force regular-expression based conversion from MySQL syntax to sqlite3 syntax. +// It is NOT meant to be a general purpose solution and is only expected & confirmed to run on +// queries issued by orchestrator. There are known limitations to this design. +// It's not even pretty. +// In fact... +// Well, it gets the job done at this time. Call it debt. + +package sqlutils + +import ( + "regexp" +) + +var sqlite3CreateTableConversions = []regexpMap{ + rmap(`(?i) (character set|charset) [\S]+`, ``), + rmap(`(?i)int unsigned`, `int`), + rmap(`(?i)int[\s]*[(][\s]*([0-9]+)[\s]*[)] unsigned`, `int`), + rmap(`(?i)engine[\s]*=[\s]*(innodb|myisam|ndb|memory|tokudb)`, ``), + rmap(`(?i)DEFAULT CHARSET[\s]*=[\s]*[\S]+`, ``), + rmap(`(?i)[\S]*int( not null|) auto_increment`, `integer`), + rmap(`(?i)comment '[^']*'`, ``), + rmap(`(?i)after [\S]+`, ``), + rmap(`(?i)alter table ([\S]+) add (index|key) ([\S]+) (.+)`, `create index ${3}_${1} on $1 $4`), + rmap(`(?i)alter table ([\S]+) add unique (index|key) ([\S]+) (.+)`, `create unique index ${3}_${1} on $1 $4`), + rmap(`(?i)([\S]+) enum[\s]*([(].*?[)])`, `$1 text check($1 in $2)`), + rmap(`(?i)([\s\S]+[/][*] sqlite3-skip [*][/][\s\S]+)`, ``), + rmap(`(?i)timestamp default current_timestamp`, `timestamp default ('')`), + rmap(`(?i)timestamp not null default current_timestamp`, `timestamp not null default ('')`), + + rmap(`(?i)add column (.*int) not null[\s]*$`, `add column $1 not null default 0`), + rmap(`(?i)add column (.* text) not null[\s]*$`, `add column $1 not null default ''`), + rmap(`(?i)add column (.* varchar.*) not null[\s]*$`, `add column $1 not null default ''`), +} + +var sqlite3InsertConversions = []regexpMap{ + rmap(`(?i)insert ignore ([\s\S]+) on duplicate key update [\s\S]+`, `insert or ignore $1`), + rmap(`(?i)insert ignore`, `insert or ignore`), + rmap(`(?i)now[(][)]`, `datetime('now')`), + rmap(`(?i)insert into ([\s\S]+) on duplicate key update [\s\S]+`, `replace into $1`), +} + +var sqlite3GeneralConversions = []regexpMap{ + rmap(`(?i)now[(][)][\s]*[-][\s]*interval [?] ([\w]+)`, `datetime('now', printf('-%d $1', ?))`), + rmap(`(?i)now[(][)][\s]*[+][\s]*interval [?] ([\w]+)`, `datetime('now', printf('+%d $1', ?))`), + rmap(`(?i)now[(][)][\s]*[-][\s]*interval ([0-9.]+) ([\w]+)`, `datetime('now', '-${1} $2')`), + rmap(`(?i)now[(][)][\s]*[+][\s]*interval ([0-9.]+) ([\w]+)`, `datetime('now', '+${1} $2')`), + + rmap(`(?i)[=<>\s]([\S]+[.][\S]+)[\s]*[-][\s]*interval [?] ([\w]+)`, ` datetime($1, printf('-%d $2', ?))`), + rmap(`(?i)[=<>\s]([\S]+[.][\S]+)[\s]*[+][\s]*interval [?] ([\w]+)`, ` datetime($1, printf('+%d $2', ?))`), + + rmap(`(?i)unix_timestamp[(][)]`, `strftime('%s', 'now')`), + rmap(`(?i)unix_timestamp[(]([^)]+)[)]`, `strftime('%s', $1)`), + rmap(`(?i)now[(][)]`, `datetime('now')`), + rmap(`(?i)cast[(][\s]*([\S]+) as signed[\s]*[)]`, `cast($1 as integer)`), + + rmap(`(?i)\bconcat[(][\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*[)]`, `($1 || $2)`), + rmap(`(?i)\bconcat[(][\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*[)]`, `($1 || $2 || $3)`), + + rmap(`(?i) rlike `, ` like `), + + rmap(`(?i)create index([\s\S]+)[(][\s]*[0-9]+[\s]*[)]([\s\S]+)`, `create index ${1}${2}`), + rmap(`(?i)drop index ([\S]+) on ([\S]+)`, `drop index if exists $1`), +} + +var ( + sqlite3IdentifyCreateTableStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*create table`)) + sqlite3IdentifyCreateIndexStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*create( unique|) index`)) + sqlite3IdentifyDropIndexStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*drop index`)) + sqlite3IdentifyAlterTableStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*alter table`)) + sqlite3IdentifyInsertStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*(insert|replace)`)) +) + +func IsInsert(statement string) bool { + return sqlite3IdentifyInsertStatement.MatchString(statement) +} + +func IsCreateTable(statement string) bool { + return sqlite3IdentifyCreateTableStatement.MatchString(statement) +} + +func IsCreateIndex(statement string) bool { + return sqlite3IdentifyCreateIndexStatement.MatchString(statement) +} + +func IsDropIndex(statement string) bool { + return sqlite3IdentifyDropIndexStatement.MatchString(statement) +} + +func IsAlterTable(statement string) bool { + return sqlite3IdentifyAlterTableStatement.MatchString(statement) +} + +func ToSqlite3CreateTable(statement string) string { + return applyConversions(statement, sqlite3CreateTableConversions) +} + +func ToSqlite3Insert(statement string) string { + return applyConversions(statement, sqlite3InsertConversions) +} + +func ToSqlite3Dialect(statement string) (translated string) { + if IsCreateTable(statement) { + return ToSqlite3CreateTable(statement) + } + if IsAlterTable(statement) { + return ToSqlite3CreateTable(statement) + } + statement = applyConversions(statement, sqlite3GeneralConversions) + if IsInsert(statement) { + return ToSqlite3Insert(statement) + } + return statement +} diff --git a/vendor/github.com/openark/golib/sqlutils/sqlite_dialect_test.go b/vendor/github.com/openark/golib/sqlutils/sqlite_dialect_test.go new file mode 100644 index 000000000..a3eea7120 --- /dev/null +++ b/vendor/github.com/openark/golib/sqlutils/sqlite_dialect_test.go @@ -0,0 +1,242 @@ +/* + Copyright 2017 GitHub Inc. + + Licensed 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. +*/ + +package sqlutils + +import ( + "regexp" + "strings" + "testing" + + test "github.com/openark/golib/tests" +) + +var spacesRegexp = regexp.MustCompile(`[\s]+`) + +func init() { +} + +func stripSpaces(statement string) string { + statement = strings.TrimSpace(statement) + statement = spacesRegexp.ReplaceAllString(statement, " ") + return statement +} + +func TestIsCreateTable(t *testing.T) { + test.S(t).ExpectTrue(IsCreateTable("create table t(id int)")) + test.S(t).ExpectTrue(IsCreateTable(" create table t(id int)")) + test.S(t).ExpectTrue(IsCreateTable("CREATE TABLE t(id int)")) + test.S(t).ExpectTrue(IsCreateTable(` + create table t(id int) + `)) + test.S(t).ExpectFalse(IsCreateTable("where create table t(id int)")) + test.S(t).ExpectFalse(IsCreateTable("insert")) +} + +func TestToSqlite3CreateTable(t *testing.T) { + { + statement := "create table t(id int)" + result := ToSqlite3CreateTable(statement) + test.S(t).ExpectEquals(result, statement) + } + { + statement := "create table t(id int, v varchar(123) CHARACTER SET ascii NOT NULL default '')" + result := ToSqlite3CreateTable(statement) + test.S(t).ExpectEquals(result, "create table t(id int, v varchar(123) NOT NULL default '')") + } + { + statement := "create table t(id int, v varchar ( 123 ) CHARACTER SET ascii NOT NULL default '')" + result := ToSqlite3CreateTable(statement) + test.S(t).ExpectEquals(result, "create table t(id int, v varchar ( 123 ) NOT NULL default '')") + } + { + statement := "create table t(i smallint unsigned)" + result := ToSqlite3CreateTable(statement) + test.S(t).ExpectEquals(result, "create table t(i smallint)") + } + { + statement := "create table t(i smallint(5) unsigned)" + result := ToSqlite3CreateTable(statement) + test.S(t).ExpectEquals(result, "create table t(i smallint)") + } + { + statement := "create table t(i smallint ( 5 ) unsigned)" + result := ToSqlite3CreateTable(statement) + test.S(t).ExpectEquals(result, "create table t(i smallint)") + } +} + +func TestToSqlite3AlterTable(t *testing.T) { + { + statement := ` + ALTER TABLE + database_instance + ADD COLUMN sql_delay INT UNSIGNED NOT NULL AFTER slave_lag_seconds + ` + result := stripSpaces(ToSqlite3Dialect(statement)) + test.S(t).ExpectEquals(result, stripSpaces(` + ALTER TABLE + database_instance + add column sql_delay int not null default 0 + `)) + } + { + statement := ` + ALTER TABLE + database_instance + ADD INDEX master_host_port_idx (master_host, master_port) + ` + result := stripSpaces(ToSqlite3Dialect(statement)) + test.S(t).ExpectEquals(result, stripSpaces(` + create index + master_host_port_idx_database_instance + on database_instance (master_host, master_port) + `)) + } + { + statement := ` + ALTER TABLE + topology_recovery + ADD KEY last_detection_idx (last_detection_id) + ` + result := stripSpaces(ToSqlite3Dialect(statement)) + test.S(t).ExpectEquals(result, stripSpaces(` + create index + last_detection_idx_topology_recovery + on topology_recovery (last_detection_id) + `)) + } + +} + +func TestCreateIndex(t *testing.T) { + { + statement := ` + create index + master_host_port_idx_database_instance + on database_instance (master_host(128), master_port) + ` + result := stripSpaces(ToSqlite3Dialect(statement)) + test.S(t).ExpectEquals(result, stripSpaces(` + create index + master_host_port_idx_database_instance + on database_instance (master_host, master_port) + `)) + } +} + +func TestIsInsert(t *testing.T) { + test.S(t).ExpectTrue(IsInsert("insert into t")) + test.S(t).ExpectTrue(IsInsert("insert ignore into t")) + test.S(t).ExpectTrue(IsInsert(` + insert ignore into t + `)) + test.S(t).ExpectFalse(IsInsert("where create table t(id int)")) + test.S(t).ExpectFalse(IsInsert("create table t(id int)")) + test.S(t).ExpectTrue(IsInsert(` + insert into + cluster_domain_name (cluster_name, domain_name, last_registered) + values + (?, ?, datetime('now')) + on duplicate key update + domain_name=values(domain_name), + last_registered=values(last_registered) + `)) +} + +func TestToSqlite3Insert(t *testing.T) { + { + statement := ` + insert into + cluster_domain_name (cluster_name, domain_name, last_registered) + values + (?, ?, datetime('now')) + on duplicate key update + domain_name=values(domain_name), + last_registered=values(last_registered) + ` + result := stripSpaces(ToSqlite3Dialect(statement)) + test.S(t).ExpectEquals(result, stripSpaces(` + replace into + cluster_domain_name (cluster_name, domain_name, last_registered) + values + (?, ?, datetime('now')) + `)) + } +} + +func TestToSqlite3GeneralConversions(t *testing.T) { + { + statement := "select now()" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select datetime('now')") + } + { + statement := "select now() - interval ? second" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select datetime('now', printf('-%d second', ?))") + } + { + statement := "select now() + interval ? minute" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select datetime('now', printf('+%d minute', ?))") + } + { + statement := "select now() + interval 5 minute" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select datetime('now', '+5 minute')") + } + { + statement := "select some_table.some_column + interval ? minute" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select datetime(some_table.some_column, printf('+%d minute', ?))") + } + { + statement := "AND master_instance.last_attempted_check <= master_instance.last_seen + interval ? minute" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "AND master_instance.last_attempted_check <= datetime(master_instance.last_seen, printf('+%d minute', ?))") + } + { + statement := "select concat(master_instance.port, '') as port" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select (master_instance.port || '') as port") + } + { + statement := "select concat( 'abc' , 'def') as s" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select ('abc' || 'def') as s") + } + { + statement := "select concat( 'abc' , 'def', last.col) as s" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select ('abc' || 'def' || last.col) as s") + } + { + statement := "select concat(myself.only) as s" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select concat(myself.only) as s") + } + { + statement := "select concat(1, '2', 3, '4') as s" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select concat(1, '2', 3, '4') as s") + } + { + statement := "select group_concat( 'abc' , 'def') as s" + result := ToSqlite3Dialect(statement) + test.S(t).ExpectEquals(result, "select group_concat( 'abc' , 'def') as s") + } +} diff --git a/vendor/github.com/openark/golib/sqlutils/sqlutils.go b/vendor/github.com/openark/golib/sqlutils/sqlutils.go new file mode 100644 index 000000000..0a2eda262 --- /dev/null +++ b/vendor/github.com/openark/golib/sqlutils/sqlutils.go @@ -0,0 +1,427 @@ +/* + Copyright 2014 Outbrain Inc. + + Licensed 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. +*/ + +package sqlutils + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/openark/golib/log" +) + +const DateTimeFormat = "2006-01-02 15:04:05.999999" + +// RowMap represents one row in a result set. Its objective is to allow +// for easy, typed getters by column name. +type RowMap map[string]CellData + +// Cell data is the result of a single (atomic) column in a single row +type CellData sql.NullString + +func (this *CellData) MarshalJSON() ([]byte, error) { + if this.Valid { + return json.Marshal(this.String) + } else { + return json.Marshal(nil) + } +} + +// UnmarshalJSON reds this object from JSON +func (this *CellData) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + (*this).String = s + (*this).Valid = true + + return nil +} + +func (this *CellData) NullString() *sql.NullString { + return (*sql.NullString)(this) +} + +// RowData is the result of a single row, in positioned array format +type RowData []CellData + +// MarshalJSON will marshal this map as JSON +func (this *RowData) MarshalJSON() ([]byte, error) { + cells := make([](*CellData), len(*this), len(*this)) + for i, val := range *this { + d := CellData(val) + cells[i] = &d + } + return json.Marshal(cells) +} + +func (this *RowData) Args() []interface{} { + result := make([]interface{}, len(*this)) + for i := range *this { + result[i] = (*(*this)[i].NullString()) + } + return result +} + +// ResultData is an ordered row set of RowData +type ResultData []RowData +type NamedResultData struct { + Columns []string + Data ResultData +} + +var EmptyResultData = ResultData{} + +func (this *RowMap) GetString(key string) string { + return (*this)[key].String +} + +// GetStringD returns a string from the map, or a default value if the key does not exist +func (this *RowMap) GetStringD(key string, def string) string { + if cell, ok := (*this)[key]; ok { + return cell.String + } + return def +} + +func (this *RowMap) GetInt64(key string) int64 { + res, _ := strconv.ParseInt(this.GetString(key), 10, 0) + return res +} + +func (this *RowMap) GetNullInt64(key string) sql.NullInt64 { + i, err := strconv.ParseInt(this.GetString(key), 10, 0) + if err == nil { + return sql.NullInt64{Int64: i, Valid: true} + } else { + return sql.NullInt64{Valid: false} + } +} + +func (this *RowMap) GetInt(key string) int { + res, _ := strconv.Atoi(this.GetString(key)) + return res +} + +func (this *RowMap) GetIntD(key string, def int) int { + res, err := strconv.Atoi(this.GetString(key)) + if err != nil { + return def + } + return res +} + +func (this *RowMap) GetUint(key string) uint { + res, _ := strconv.ParseUint(this.GetString(key), 10, 0) + return uint(res) +} + +func (this *RowMap) GetUintD(key string, def uint) uint { + res, err := strconv.Atoi(this.GetString(key)) + if err != nil { + return def + } + return uint(res) +} + +func (this *RowMap) GetUint64(key string) uint64 { + res, _ := strconv.ParseUint(this.GetString(key), 10, 0) + return res +} + +func (this *RowMap) GetUint64D(key string, def uint64) uint64 { + res, err := strconv.ParseUint(this.GetString(key), 10, 0) + if err != nil { + return def + } + return uint64(res) +} + +func (this *RowMap) GetBool(key string) bool { + return this.GetInt(key) != 0 +} + +func (this *RowMap) GetTime(key string) time.Time { + if t, err := time.Parse(DateTimeFormat, this.GetString(key)); err == nil { + return t + } + return time.Time{} +} + +// knownDBs is a DB cache by uri +var knownDBs map[string]*sql.DB = make(map[string]*sql.DB) +var knownDBsMutex = &sync.Mutex{} + +// GetDB returns a DB instance based on uri. +// bool result indicates whether the DB was returned from cache; err +func GetGenericDB(driverName, dataSourceName string) (*sql.DB, bool, error) { + knownDBsMutex.Lock() + defer func() { + knownDBsMutex.Unlock() + }() + + var exists bool + if _, exists = knownDBs[dataSourceName]; !exists { + if db, err := sql.Open(driverName, dataSourceName); err == nil { + knownDBs[dataSourceName] = db + } else { + return db, exists, err + } + } + return knownDBs[dataSourceName], exists, nil +} + +// GetDB returns a MySQL DB instance based on uri. +// bool result indicates whether the DB was returned from cache; err +func GetDB(mysql_uri string) (*sql.DB, bool, error) { + return GetGenericDB("mysql", mysql_uri) +} + +// GetDB returns a SQLite DB instance based on DB file name. +// bool result indicates whether the DB was returned from cache; err +func GetSQLiteDB(dbFile string) (*sql.DB, bool, error) { + return GetGenericDB("sqlite3", dbFile) +} + +// RowToArray is a convenience function, typically not called directly, which maps a +// single read database row into a NullString +func RowToArray(rows *sql.Rows, columns []string) []CellData { + buff := make([]interface{}, len(columns)) + data := make([]CellData, len(columns)) + for i, _ := range buff { + buff[i] = data[i].NullString() + } + rows.Scan(buff...) + return data +} + +// ScanRowsToArrays is a convenience function, typically not called directly, which maps rows +// already read from the databse into arrays of NullString +func ScanRowsToArrays(rows *sql.Rows, on_row func([]CellData) error) error { + columns, _ := rows.Columns() + for rows.Next() { + arr := RowToArray(rows, columns) + err := on_row(arr) + if err != nil { + return err + } + } + return nil +} + +func rowToMap(row []CellData, columns []string) map[string]CellData { + m := make(map[string]CellData) + for k, data_col := range row { + m[columns[k]] = data_col + } + return m +} + +// ScanRowsToMaps is a convenience function, typically not called directly, which maps rows +// already read from the databse into RowMap entries. +func ScanRowsToMaps(rows *sql.Rows, on_row func(RowMap) error) error { + columns, _ := rows.Columns() + err := ScanRowsToArrays(rows, func(arr []CellData) error { + m := rowToMap(arr, columns) + err := on_row(m) + if err != nil { + return err + } + return nil + }) + return err +} + +// QueryRowsMap is a convenience function allowing querying a result set while poviding a callback +// function activated per read row. +func QueryRowsMap(db *sql.DB, query string, on_row func(RowMap) error, args ...interface{}) (err error) { + defer func() { + if derr := recover(); derr != nil { + err = fmt.Errorf("QueryRowsMap unexpected error: %+v", derr) + } + }() + + var rows *sql.Rows + rows, err = db.Query(query, args...) + if rows != nil { + defer rows.Close() + } + if err != nil && err != sql.ErrNoRows { + return log.Errore(err) + } + err = ScanRowsToMaps(rows, on_row) + return +} + +// queryResultData returns a raw array of rows for a given query, optionally reading and returning column names +func queryResultData(db *sql.DB, query string, retrieveColumns bool, args ...interface{}) (resultData ResultData, columns []string, err error) { + defer func() { + if derr := recover(); derr != nil { + err = errors.New(fmt.Sprintf("QueryRowsMap unexpected error: %+v", derr)) + } + }() + + var rows *sql.Rows + rows, err = db.Query(query, args...) + defer rows.Close() + if err != nil && err != sql.ErrNoRows { + return EmptyResultData, columns, err + } + if retrieveColumns { + // Don't pay if you don't want to + columns, _ = rows.Columns() + } + resultData = ResultData{} + err = ScanRowsToArrays(rows, func(rowData []CellData) error { + resultData = append(resultData, rowData) + return nil + }) + return resultData, columns, err +} + +// QueryResultData returns a raw array of rows +func QueryResultData(db *sql.DB, query string, args ...interface{}) (ResultData, error) { + resultData, _, err := queryResultData(db, query, false, args...) + return resultData, err +} + +// QueryResultDataNamed returns a raw array of rows, with column names +func QueryNamedResultData(db *sql.DB, query string, args ...interface{}) (NamedResultData, error) { + resultData, columns, err := queryResultData(db, query, true, args...) + return NamedResultData{Columns: columns, Data: resultData}, err +} + +// QueryRowsMapBuffered reads data from the database into a buffer, and only then applies the given function per row. +// This allows the application to take its time with processing the data, albeit consuming as much memory as required by +// the result set. +func QueryRowsMapBuffered(db *sql.DB, query string, on_row func(RowMap) error, args ...interface{}) error { + resultData, columns, err := queryResultData(db, query, true, args...) + if err != nil { + // Already logged + return err + } + for _, row := range resultData { + err = on_row(rowToMap(row, columns)) + if err != nil { + return err + } + } + return nil +} + +// ExecNoPrepare executes given query using given args on given DB, without using prepared statements. +func ExecNoPrepare(db *sql.DB, query string, args ...interface{}) (res sql.Result, err error) { + defer func() { + if derr := recover(); derr != nil { + err = errors.New(fmt.Sprintf("ExecNoPrepare unexpected error: %+v", derr)) + } + }() + + res, err = db.Exec(query, args...) + if err != nil { + log.Errore(err) + } + return res, err +} + +// ExecQuery executes given query using given args on given DB. It will safele prepare, execute and close +// the statement. +func execInternal(silent bool, db *sql.DB, query string, args ...interface{}) (res sql.Result, err error) { + defer func() { + if derr := recover(); derr != nil { + err = errors.New(fmt.Sprintf("execInternal unexpected error: %+v", derr)) + } + }() + var stmt *sql.Stmt + stmt, err = db.Prepare(query) + if err != nil { + return nil, err + } + defer stmt.Close() + res, err = stmt.Exec(args...) + if err != nil && !silent { + log.Errore(err) + } + return res, err +} + +// Exec executes given query using given args on given DB. It will safele prepare, execute and close +// the statement. +func Exec(db *sql.DB, query string, args ...interface{}) (sql.Result, error) { + return execInternal(false, db, query, args...) +} + +// ExecSilently acts like Exec but does not report any error +func ExecSilently(db *sql.DB, query string, args ...interface{}) (sql.Result, error) { + return execInternal(true, db, query, args...) +} + +func InClauseStringValues(terms []string) string { + quoted := []string{} + for _, s := range terms { + quoted = append(quoted, fmt.Sprintf("'%s'", strings.Replace(s, ",", "''", -1))) + } + return strings.Join(quoted, ", ") +} + +// Convert variable length arguments into arguments array +func Args(args ...interface{}) []interface{} { + return args +} + +func NilIfZero(i int64) interface{} { + if i == 0 { + return nil + } + return i +} + +func ScanTable(db *sql.DB, tableName string) (NamedResultData, error) { + query := fmt.Sprintf("select * from %s", tableName) + return QueryNamedResultData(db, query) +} + +func WriteTable(db *sql.DB, tableName string, data NamedResultData) (err error) { + if len(data.Data) == 0 { + return nil + } + if len(data.Columns) == 0 { + return nil + } + placeholders := make([]string, len(data.Columns)) + for i := range placeholders { + placeholders[i] = "?" + } + query := fmt.Sprintf( + `replace into %s (%s) values (%s)`, + tableName, + strings.Join(data.Columns, ","), + strings.Join(placeholders, ","), + ) + for _, rowData := range data.Data { + if _, execErr := db.Exec(query, rowData.Args()...); execErr != nil { + err = execErr + } + } + return err +} diff --git a/vendor/github.com/openark/golib/tests/spec.go b/vendor/github.com/openark/golib/tests/spec.go new file mode 100644 index 000000000..a52c7291a --- /dev/null +++ b/vendor/github.com/openark/golib/tests/spec.go @@ -0,0 +1,76 @@ +package tests + +import ( + "testing" +) + +// Spec is an access point to test Expections +type Spec struct { + t *testing.T +} + +// S generates a spec. You will want to use it once in a test file, once in a test or once per each check +func S(t *testing.T) *Spec { + return &Spec{t: t} +} + +// ExpectNil expects given value to be nil, or errors +func (spec *Spec) ExpectNil(actual interface{}) { + if actual == nil { + return + } + spec.t.Errorf("Expected %+v to be nil", actual) +} + +// ExpectNotNil expects given value to be not nil, or errors +func (spec *Spec) ExpectNotNil(actual interface{}) { + if actual != nil { + return + } + spec.t.Errorf("Expected %+v to be not nil", actual) +} + +// ExpectEquals expects given values to be equal (comparison via `==`), or errors +func (spec *Spec) ExpectEquals(actual, value interface{}) { + if actual == value { + return + } + spec.t.Errorf("Expected:\n[[[%+v]]]\n- got:\n[[[%+v]]]", value, actual) +} + +// ExpectNotEquals expects given values to be nonequal (comparison via `==`), or errors +func (spec *Spec) ExpectNotEquals(actual, value interface{}) { + if !(actual == value) { + return + } + spec.t.Errorf("Expected not %+v", value) +} + +// ExpectEqualsAny expects given actual to equal (comparison via `==`) at least one of given values, or errors +func (spec *Spec) ExpectEqualsAny(actual interface{}, values ...interface{}) { + for _, value := range values { + if actual == value { + return + } + } + spec.t.Errorf("Expected %+v to equal any of given values", actual) +} + +// ExpectNotEqualsAny expects given actual to be nonequal (comparison via `==`)tp any of given values, or errors +func (spec *Spec) ExpectNotEqualsAny(actual interface{}, values ...interface{}) { + for _, value := range values { + if actual == value { + spec.t.Errorf("Expected not %+v", value) + } + } +} + +// ExpectFalse expects given values to be false, or errors +func (spec *Spec) ExpectFalse(actual interface{}) { + spec.ExpectEquals(actual, false) +} + +// ExpectTrue expects given values to be true, or errors +func (spec *Spec) ExpectTrue(actual interface{}) { + spec.ExpectEquals(actual, true) +} diff --git a/vendor/github.com/openark/golib/util/text.go b/vendor/github.com/openark/golib/util/text.go new file mode 100644 index 000000000..2b0ae9a7e --- /dev/null +++ b/vendor/github.com/openark/golib/util/text.go @@ -0,0 +1,103 @@ +/* + Copyright 2015 Shlomi Noach. + + Licensed 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. +*/ + +package util + +import ( + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +const ( + TabulateLeft = 0 + TabulateRight = 1 +) + +// ParseSimpleTime parses input in the format 7s, 55m, 3h, 31d, 4w (second, minute, hour, day, week) +// The time.ParseDuration() function should have done this, but it does not support "d" and "w" extensions. +func SimpleTimeToSeconds(simpleTime string) (int, error) { + if matched, _ := regexp.MatchString("^[0-9]+s$", simpleTime); matched { + i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1]) + return i, nil + } + if matched, _ := regexp.MatchString("^[0-9]+m$", simpleTime); matched { + i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1]) + return i * 60, nil + } + if matched, _ := regexp.MatchString("^[0-9]+h$", simpleTime); matched { + i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1]) + return i * 60 * 60, nil + } + if matched, _ := regexp.MatchString("^[0-9]+d$", simpleTime); matched { + i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1]) + return i * 60 * 60 * 24, nil + } + if matched, _ := regexp.MatchString("^[0-9]+w$", simpleTime); matched { + i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1]) + return i * 60 * 60 * 24 * 7, nil + } + return 0, errors.New(fmt.Sprintf("Cannot parse simple time: %s", simpleTime)) +} + +func Tabulate(lines []string, separator string, outputSeparator string, directionFlags ...int) (result []string) { + tokens := make([][]string, 0) + widths := make([][]int, 0) + countColumns := 0 + for _, line := range lines { + lineTokens := strings.Split(line, separator) + lineWidths := make([]int, len(lineTokens)) + for i := range lineTokens { + lineWidths[i] = len(lineTokens[i]) + } + tokens = append(tokens, lineTokens) + widths = append(widths, lineWidths) + if len(lineTokens) > countColumns { + countColumns = len(lineTokens) + } + } + columnWidths := make([]int, countColumns) + for _, lineTokens := range tokens { + for col, token := range lineTokens { + if len(token) > columnWidths[col] { + columnWidths[col] = len(token) + } + } + } + for _, lineTokens := range tokens { + resultRow := "" + for col := 0; col < countColumns; col++ { + token := "" + if col < len(lineTokens) { + token = lineTokens[col] + } + format := fmt.Sprintf("%%-%ds", columnWidths[col]) // format left + if col < len(directionFlags) && directionFlags[col] == TabulateRight { + format = fmt.Sprintf("%%%ds", columnWidths[col]) + } + formattedToken := fmt.Sprintf(format, token) + if col == 0 { + resultRow = formattedToken + } else { + resultRow = fmt.Sprintf("%s%s%s", resultRow, outputSeparator, formattedToken) + } + } + result = append(result, resultRow) + } + return result +} diff --git a/vendor/github.com/openark/golib/util/text_test.go b/vendor/github.com/openark/golib/util/text_test.go new file mode 100644 index 000000000..aa5052d18 --- /dev/null +++ b/vendor/github.com/openark/golib/util/text_test.go @@ -0,0 +1,88 @@ +/* + Copyright 2014 Outbrain Inc. + + Licensed 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. +*/ + +package util + +import ( + "reflect" + "strings" + "testing" + + test "github.com/openark/golib/tests" +) + +func init() { +} + +func TestTabulate(t *testing.T) { + { + text := strings.TrimSpace(` +a,b,c +d,e,f +g,h,i + `) + + tabulated := Tabulate(strings.Split(text, "\n"), ",", ",") + expected := strings.Split(text, "\n") + test.S(t).ExpectTrue(reflect.DeepEqual(tabulated, expected)) + } + { + text := strings.TrimSpace(` +a,b,c +d,e,f +g,h,i + `) + + tabulated := Tabulate(strings.Split(text, "\n"), ",", "|") + expected := []string{ + "a|b|c", + "d|e|f", + "g|h|i", + } + test.S(t).ExpectTrue(reflect.DeepEqual(tabulated, expected)) + } + { + text := strings.TrimSpace(` +a,20,c +d,e,100 +0000,h,i + `) + + tabulated := Tabulate(strings.Split(text, "\n"), ",", "|") + expected := []string{ + "a |20|c ", + "d |e |100", + "0000|h |i ", + } + test.S(t).ExpectTrue(reflect.DeepEqual(tabulated, expected)) + } + { + text := strings.TrimSpace(` +a,20,c +d,1,100 +0000,3,i + `) + + tabulated := Tabulate(strings.Split(text, "\n"), ",", "|", TabulateLeft, TabulateRight, TabulateRight) + expected := []string{ + "a |20| c", + "d | 1|100", + "0000| 3| i", + } + + test.S(t).ExpectTrue(reflect.DeepEqual(tabulated, expected)) + } +}