Compare commits

..

31 Commits
main ... master

Author SHA1 Message Date
ycc
ec1a5a2496 license file added 2024-01-04 14:50:24 +01:00
ycc
a944094978 added wildDelete 2023-09-29 09:30:38 +02:00
ycc
9f1f7476ab added logging capability 2023-09-28 17:28:38 +02:00
ycc
be081e6e75 Add Logging feature attribute 2023-09-28 16:56:47 +02:00
ycc
d457fc76b8 debug temp 2023-09-26 16:27:07 +02:00
ycc
8398abca7d old mysql patch ? 2023-09-26 14:57:30 +02:00
ycc
19e7157c7b better unknown type log 2023-09-26 12:04:50 +02:00
ycc
fb6955465a deprecated funcs fix 2023-08-22 21:35:14 +02:00
ycc
c362b6bc33 result to map 2023-07-07 09:15:22 +02:00
ycc
cde5fe7366 change ids to int64 2023-07-05 14:37:41 +02:00
ycc
4a6840fd2f bugfix insert lastid in mysql 2023-07-05 14:18:44 +02:00
ycc
7a4c6eb35d added unsigned int to mysql types 2023-07-04 09:46:33 +02:00
ycc
5ce4f94524 mysql OK, partially tested 2023-07-03 15:26:16 +02:00
ycc
31690925c1 mysql OK, partially tested 2023-07-03 15:26:07 +02:00
ycc
974fab1d64 adding mysql support, table creation tested, TODO schema 2023-06-27 23:19:50 +02:00
cf652439b4 add breaks once id treated 2023-01-30 12:16:57 +01:00
eb6e33cb2e empty table 2023-01-23 11:29:46 +01:00
ycc
4e17758e15 nil deserialize 2022-05-20 15:12:04 +02:00
ycc
85d2790bb2 select null management 2022-05-20 11:00:14 +02:00
ycc
d99db63940 fix insert err return & timestamp formatting 2022-05-19 10:49:19 +02:00
ycc
df398dab1a Null management 2022-05-17 17:03:38 +02:00
ycc
c9614975a6 survey added 2022-04-20 15:22:31 +02:00
ycc
dee183ae76 schema and template functions 2021-12-03 11:19:53 +01:00
ycc
f406c17ea9 schema tbale list label update 2021-11-09 13:49:52 +01:00
ycc
cac8a47bbe sort split on empty string terurnt array of one empty string 2021-11-03 15:11:48 +01:00
ycc
523f25edde Column comments added 2021-10-30 22:21:54 +02:00
ycc
712a5f9ca5 Quote utility function added 2021-10-28 15:31:36 +02:00
ycc
d914b7d905 add utility functions 2021-10-28 15:12:53 +02:00
ycc
b16fa31515 Merge branch 'main'
Initial fusion
2021-10-28 10:58:53 +02:00
ycc
2c42960b93 merge conflicts 2021-10-28 10:58:48 +02:00
a06f61b8b7 Initial commit 2021-10-27 16:31:31 +00:00
19 changed files with 1419 additions and 367 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@
# Dependency directories (remove the comment below to include it)
# vendor/
__debug_bin
gen/

121
LICENSE Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# sqldb

769
db.go Executable file
View File

@ -0,0 +1,769 @@
package sqldb
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"html/template"
"os"
"reflect"
"strconv"
"strings"
_ "github.com/go-sql-driver/mysql"
"github.com/lib/pq"
"github.com/rs/zerolog"
)
var log zerolog.Logger
type Db struct {
Driver string
Url string
LogQueries bool
conn *sql.DB
}
// AssRow : associative row type
type AssRow map[string]interface{}
// Select Result
type Rows []AssRow
// Table is a table structure description
type TableInfo struct {
Name string `json:"name"`
Columns map[string]string `json:"columns"`
db *Db
}
type Link struct {
Source string
Destination string
}
// Open the database
func Open(driver string, url string) *Db {
var database Db
var err error
database.Driver = driver
database.Url = url
database.conn, err = sql.Open(driver, url)
if err != nil {
log.Error().Msg(err.Error())
}
return &database
}
// Close the database connection
func (db *Db) Close() {
db.conn.Close()
}
func (db *Db) Table(name string) *TableInfo {
var ti TableInfo
ti.Name = name
ti.db = db
return &ti
}
// GetAssociativeArray : Provide table data as an associative array
func (t *TableInfo) GetAssociativeArray(columns []string, restriction string, sortkeys []string, dir string) ([]AssRow, error) {
return t.db.QueryAssociativeArray(t.buildSelect("", columns, restriction, sortkeys, dir))
}
// QueryAssociativeArray : Provide query result as an associative array
func (db *Db) QueryAssociativeArray(query string) (Rows, error) {
if db.LogQueries {
log.Info().Msg(query)
}
rows, err := db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
log.Error().Msg(query)
return nil, err
}
defer rows.Close()
// get rows
results := Rows{}
cols, err := rows.Columns()
if err != nil {
log.Error().Msg(err.Error())
log.Error().Msg(query)
return nil, err
}
// make types map
columnTypes, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
columnType := make(map[string]string)
for _, colType := range columnTypes {
columnType[colType.Name()] = colType.DatabaseTypeName()
}
for rows.Next() {
// Create a slice of interface{}'s to represent each column,
// and a second slice to contain pointers to each item in the columns slice.
columns := make([]interface{}, len(cols))
columnPointers := make([]interface{}, len(cols))
for i := range columns {
columnPointers[i] = &columns[i]
}
// Scan the result into the column pointers...
err = rows.Scan(columnPointers...)
if err != nil {
return nil, err
}
// Create our map, and retrieve the value for each column from the pointers slice,
// storing it in the map with the name of the column as the key.
m := make(AssRow)
for i, colName := range cols {
//fmt.Println(colName)
val := columnPointers[i].(*interface{})
if db.Driver == "mysql" {
if (*val) == nil {
m[colName] = nil
} else {
switch columnType[colName] {
case "INT", "BIGINT":
i, err := strconv.ParseInt(fmt.Sprintf("%s", *val), 10, 64)
if err != nil {
return nil, err
}
m[colName] = i
case "UNSIGNED BIGINT", "UNSIGNED INT":
u, err := strconv.ParseUint(fmt.Sprintf("%s", *val), 10, 64)
if err != nil {
return nil, err
}
m[colName] = u
case "FLOAT":
f, err := strconv.ParseFloat(fmt.Sprintf("%s", *val), 64)
if err != nil {
return nil, err
}
m[colName] = f
case "TINYINT":
i, err := strconv.ParseInt(fmt.Sprintf("%s", *val), 10, 64)
if err != nil {
return nil, err
}
if i == 1 {
m[colName] = true
} else {
m[colName] = false
}
case "VARCHAR", "TEXT", "TIMESTAMP", "VARBINARY":
m[colName] = fmt.Sprintf("%s", *val)
default:
if reflect.ValueOf(val).IsNil() {
m[colName] = nil
} else {
fmt.Printf("Unknow type : %s (%s)\n", columnType[colName], query)
m[colName] = fmt.Sprintf("%v", *val)
}
}
}
}
if db.Driver == "postgres" {
m[colName] = *val
}
}
results = append(results, m)
}
return results, nil
}
// GetSchema : Provide table schema as an associative array
func (t *TableInfo) GetSchema() (*TableInfo, error) {
pgSchema := "SELECT column_name :: varchar as name, REPLACE(REPLACE(data_type,'character varying','varchar'),'character','char') || COALESCE('(' || character_maximum_length || ')', '') as type, col_description('public." + t.Name + "'::regclass, ordinal_position) as comment from INFORMATION_SCHEMA.COLUMNS where table_name ='" + t.Name + "';"
mySchema := "SELECT COLUMN_NAME as name, CONCAT(DATA_TYPE, COALESCE(CONCAT('(' , CHARACTER_MAXIMUM_LENGTH, ')'), '')) as type FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" + t.Name + "';"
var schemaQuery string
var ti TableInfo
ti.Name = t.Name
ti.db = t.db
if t.db.Driver == "postgres" {
schemaQuery = pgSchema
}
if t.db.Driver == "mysql" {
schemaQuery = mySchema
}
cols, err := t.db.QueryAssociativeArray(schemaQuery)
if err != nil {
log.Error().Msg(err.Error())
return nil, err
}
ti.Columns = make(map[string]string)
for _, row := range cols {
var name, rowtype, comment string
for key, element := range row {
if key == "name" {
name = fmt.Sprintf("%s", element)
}
if key == "type" {
rowtype = fmt.Sprintf("%s", element)
}
if key == "comment" {
comment = fmt.Sprintf("%s", element)
}
}
ti.Columns[name] = rowtype
if comment != "<nil>" && strings.TrimSpace(comment) != "" {
ti.Columns[name] = ti.Columns[name] + "|" + comment
}
}
return &ti, nil
}
// GetSchema : Provide full database schema as an associative array
func (db *Db) GetSchema() ([]TableInfo, error) {
var res []TableInfo
tables, err := db.ListTables()
if err != nil {
log.Error().Msg(err.Error())
return nil, err
}
for _, row := range tables {
for _, element := range row {
var ti TableInfo
var fullti *TableInfo
ti.Name = fmt.Sprintf("%v", element)
ti.db = db
fullti, err = ti.GetSchema()
if err != nil {
log.Error().Msg(err.Error())
return nil, err
}
res = append(res, *fullti)
}
}
return res, nil
}
// GetSchema : Provide database tables list
func (db *Db) ListTables() (Rows, error) {
if db.Driver == "postgres" {
return db.pgListTables()
}
if db.Driver == "mysql" {
return db.myListTables()
}
return nil, errors.New("no driver")
}
func (db *Db) pgListTables() (Rows, error) {
return db.QueryAssociativeArray("SELECT table_name :: varchar as name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;")
}
func (db *Db) myListTables() (Rows, error) {
return db.QueryAssociativeArray("SELECT TABLE_NAME as name FROM information_schema.TABLES WHERE TABLE_TYPE LIKE 'BASE_TABLE';")
}
func (db *Db) CreateTable(t TableInfo) error {
if db.Driver == "postgres" {
return db.pgCreateTable(t)
}
if db.Driver == "mysql" {
return db.myCreateTable(t)
}
return errors.New("no driver")
}
func (db *Db) pgCreateTable(t TableInfo) error {
t.db = db
query := "create table " + t.Name + " ( "
columns := ""
for name, rowtype := range t.Columns {
if fmt.Sprintf("%v", name) == "id" {
columns += fmt.Sprintf("%v", name) + " " + "SERIAL PRIMARY KEY,"
} else {
desc := strings.Split(fmt.Sprintf("%v", rowtype), "|")
columns += fmt.Sprintf("%v", name) + " " + desc[0]
columns += ","
}
}
query += columns
query = query[:len(query)-1] + " )"
if db.LogQueries {
log.Info().Msg(query)
}
_, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
for name, rowtype := range t.Columns {
desc := strings.Split(fmt.Sprintf("%v", rowtype), "|")
if len(desc) > 1 {
query = "COMMENT ON COLUMN " + t.Name + "." + fmt.Sprintf("%v", name) + " IS '" + desc[1] + "'"
if db.LogQueries {
log.Info().Msg(query)
}
_, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
}
}
return nil
}
func (db *Db) myCreateTable(t TableInfo) error {
t.db = db
query := "create table " + t.Name + " ( "
columns := ""
for name, rowtype := range t.Columns {
if fmt.Sprintf("%v", name) == "id" {
columns += fmt.Sprintf("%v", name) + " " + "SERIAL PRIMARY KEY,"
} else {
desc := strings.Split(fmt.Sprintf("%v", rowtype), "|")
columns += fmt.Sprintf("%v", name) + " " + desc[0]
if len(desc) > 1 {
columns += " COMMENT " + pq.QuoteLiteral(desc[1])
}
columns += ","
}
}
query += columns
query = query[:len(query)-1] + " )"
if db.LogQueries {
log.Info().Msg(query)
}
_, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
return nil
}
func (t *TableInfo) DeleteTable() error {
query := "drop table " + t.Name
if t.db.LogQueries {
log.Info().Msg(query)
}
_, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
query = "drop sequence if exists sq_" + t.Name
if t.db.LogQueries {
log.Info().Msg(query)
}
_, err = t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
return nil
}
func (t *TableInfo) AddColumn(name string, sqltype string, comment string) error {
if t.db.Driver == "postgres" {
return t.pgAddColumn(name, sqltype, comment)
}
if t.db.Driver == "mysql" {
return t.myAddColumn(name, sqltype, comment)
}
return errors.New("no driver")
}
func (t *TableInfo) pgAddColumn(name string, sqltype string, comment string) error {
query := "alter table " + t.Name + " add " + name + " " + sqltype
if t.db.LogQueries {
log.Info().Msg(query)
}
rows, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
if strings.TrimSpace(comment) != "" {
query = "COMMENT ON COLUMN " + t.Name + "." + name + " IS '" + comment + "'"
if t.db.LogQueries {
log.Info().Msg(query)
}
_, err = t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
}
defer rows.Close()
return nil
}
func (t *TableInfo) myAddColumn(name string, sqltype string, comment string) error {
query := "alter table " + t.Name + " add " + name + " " + sqltype
if strings.TrimSpace(comment) != "" {
query += " COMMENT " + pq.QuoteLiteral(comment)
}
if t.db.LogQueries {
log.Info().Msg(query)
}
rows, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
defer rows.Close()
return nil
}
func (t *TableInfo) DeleteColumn(name string) error {
query := "alter table " + t.Name + " drop " + name
if t.db.LogQueries {
log.Info().Msg(query)
}
rows, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(err.Error())
return err
}
defer rows.Close()
return nil
}
func (db *Db) ImportSchema(filename string) {
byteValue, _ := os.ReadFile(filename)
var jsonSource []TableInfo
err := json.Unmarshal([]byte(byteValue), &jsonSource)
if err != nil {
log.Error().Msg(err.Error())
}
for _, ti := range jsonSource {
ti.db = db
err = db.CreateTable(ti)
if err != nil {
log.Error().Msg(err.Error())
}
}
}
func (db *Db) ClearImportSchema(filename string) {
byteValue, _ := os.ReadFile(filename)
var jsonSource []TableInfo
json.Unmarshal([]byte(byteValue), &jsonSource)
for _, ti := range jsonSource {
ti.db = db
err := ti.DeleteTable()
if err != nil {
log.Error().Msg(err.Error())
}
}
}
func (db *Db) ListSequences() (Rows, error) {
return db.QueryAssociativeArray("SELECT sequence_name :: varchar FROM information_schema.sequences WHERE sequence_schema = 'public' ORDER BY sequence_name;")
}
func (t *TableInfo) buildSelect(key string, columns []string, restriction string, sortkeys []string, dir ...string) string {
if key != "" {
columns = append(columns, key)
}
query := "select " + strings.Join(columns, ",") + " from " + t.Name
if restriction != "" {
query += " where " + restriction
}
if len(sortkeys) > 0 && len(sortkeys[0]) > 0 {
query += " order by " + strings.Join(sortkeys, ",")
}
if len(dir) > 0 {
query += " " + dir[0]
}
return query
}
func (t *TableInfo) Insert(record AssRow) (int64, error) {
columns := ""
values := ""
t, err := t.GetSchema()
if err != nil {
log.Error().Msg(err.Error())
return -1, err
}
var id int64
for key, element := range record {
columns += key + ","
values += FormatForSQL(t.Columns[key], element) + ","
}
if t.db.Driver == "postgres" {
query := "INSERT INTO " + t.Name + "(" + removeLastChar(columns) + ") VALUES (" + removeLastChar(values) + ") RETURNING id"
if t.db.LogQueries {
log.Info().Msg(query)
}
err = t.db.conn.QueryRow(query).Scan(&id)
}
if t.db.Driver == "mysql" {
/* _, err = t.db.conn.Query("INSERT INTO " + t.Name + "(" + removeLastChar(columns) + ") VALUES (" + removeLastChar(values) + ")")
if err != nil {
return id, err
}
err = t.db.conn.QueryRow("SELECT LAST_INSERT_ID()").Scan(&id)*/
query := "INSERT INTO " + t.Name + "(" + removeLastChar(columns) + ") VALUES (" + removeLastChar(values) + ")"
stmt, err := t.db.conn.Prepare(query)
if t.db.LogQueries {
log.Info().Msg(query)
}
fmt.Println("INSERT INTO " + t.Name + "(" + removeLastChar(columns) + ") VALUES (" + removeLastChar(values) + ")")
if err != nil {
return id, err
}
res, err := stmt.Exec()
if err != nil {
return id, err
}
id, err = res.LastInsertId()
if err != nil {
return id, err
}
}
return id, err
}
func (t *TableInfo) Update(record AssRow) error {
t, err := t.GetSchema()
if err != nil {
log.Error().Msg(err.Error())
return err
}
id := ""
stack := ""
for key, element := range record {
if key == "id" {
id = fmt.Sprintf("%v", element)
} else {
stack = stack + " " + key + " = " + FormatForSQL(t.Columns[key], element) + ","
}
}
stack = removeLastChar(stack)
query := ("UPDATE " + t.Name + " SET " + stack + " WHERE id = " + id)
if t.db.LogQueries {
log.Info().Msg(query)
}
rows, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(query)
log.Error().Msg(err.Error())
return err
}
defer rows.Close()
return nil
}
func (t *TableInfo) Delete(record AssRow) error {
id := ""
values := ""
for key, element := range record {
if key == "id" {
values += fmt.Sprintf("%v", element) + ","
id = removeLastChar(values)
break
}
}
query := ("DELETE FROM " + t.Name + " WHERE id = " + id)
if t.db.LogQueries {
log.Info().Msg(query)
}
rows, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(query)
log.Error().Msg(err.Error())
return err
}
defer rows.Close()
return nil
}
func (t *TableInfo) WildDelete(restriction string) error {
query := ("DELETE FROM " + t.Name + " WHERE " + restriction)
if t.db.LogQueries {
log.Info().Msg(query)
}
rows, err := t.db.conn.Query(query)
if err != nil {
log.Error().Msg(query)
log.Error().Msg(err.Error())
return err
}
defer rows.Close()
return nil
}
func (t *TableInfo) UpdateOrInsert(record AssRow) (int64, error) {
var id int64
id = -1
for key, element := range record {
if key == "id" {
sid := fmt.Sprintf("%v", element)
id, _ = strconv.ParseInt(sid, 10, 64)
break
}
}
if id == -1 {
return t.Insert(record)
} else {
t.Update(record)
return id, nil
}
}
func removeLastChar(s string) string {
r := []rune(s)
return string(r[:len(r)-1])
}
func (ar *AssRow) GetString(column string) string {
str := fmt.Sprintf("%v", (*ar)[column])
return str
}
func (ar *AssRow) GetInt(column string) int {
str := fmt.Sprintf("%v", (*ar)[column])
val, _ := strconv.Atoi(str)
return val
}
func (ar *AssRow) GetFloat(column string) float64 {
str := fmt.Sprintf("%v", (*ar)[column])
val, _ := strconv.ParseFloat(str, 64)
return val
}
func Quote(str string) string {
return pq.QuoteLiteral(str)
}
func (db *Db) SaveSchema(generatedFilename string) error {
schema, err := db.GetSchema()
if err != nil {
log.Error().Msg(err.Error())
return err
}
// file, _ := json.Marshal(schema)
file, _ := json.MarshalIndent(schema, "", " ")
_ = os.WriteFile(generatedFilename, file, 0644)
return nil
}
func buildLinks(schema []TableInfo) []Link {
var links []Link
for _, ti := range schema {
fmt.Println(ti.Name)
for column, _ := range ti.Columns {
if strings.HasSuffix(column, "_id") {
tokens := strings.Split(column, "_")
linkedtable := tokens[len(tokens)-2]
var link Link
link.Source = ti.Name
link.Destination = linkedtable
links = append(links, link)
}
}
}
return links
}
// Generate templates from a schema
func (db *Db) GenerateSchemaTemplate(templateFilename string, generatedFilename string) error {
schema, err := db.GetSchema()
if err != nil {
log.Error().Msg(err.Error())
return err
}
links := buildLinks(schema)
data := struct {
Tbl []TableInfo
Lnk []Link
}{
schema,
links,
}
t, err := template.ParseFiles(templateFilename)
if err != nil {
log.Error().Msg(err.Error())
return err
}
f, err := os.Create(generatedFilename)
if err != nil {
log.Error().Msg("create file: " + err.Error())
return err
}
err = t.Execute(f, data)
if err != nil {
log.Error().Msg(err.Error())
return err
}
return nil
}
// Generate tables
func (db *Db) GenerateTableTemplates(templateFilename string, outputFolder string, extension string) error {
schema, err := db.GetSchema()
if err != nil {
log.Error().Msg(err.Error())
return err
}
for _, ti := range schema {
t, err := template.ParseFiles(templateFilename)
if err != nil {
log.Error().Msg(err.Error())
return err
}
f, err := os.Create(outputFolder + string(os.PathSeparator) + ti.Name + "." + extension)
if err != nil {
log.Error().Msg("create file: " + err.Error())
return err
}
err = t.Execute(f, ti)
if err != nil {
log.Error().Msg(err.Error())
return err
}
}
return nil
}
func FormatForSQL(datatype string, value interface{}) string {
if value == nil {
return "NULL"
}
strval := fmt.Sprintf("%v", value)
if !strings.Contains(datatype, "char") && len(strval) == 0 {
return "NULL"
}
if strings.Contains(datatype, "char") || strings.Contains(datatype, "text") || strings.Contains(datatype, "date") || strings.Contains(datatype, "timestamp") {
return fmt.Sprint(pq.QuoteLiteral(strval))
}
return fmt.Sprint(strval)
}
// Build a map based on id from a query result
func (db *Db) BuildIdMap(idxkey string, rows Rows) (map[int64]AssRow, error) {
ht := make(map[int64]AssRow)
for _, row := range rows {
id := row[idxkey].(int64)
ht[id] = row
}
return ht, nil
}

29
docker-compose.yml Normal file
View File

@ -0,0 +1,29 @@
version: '3.1'
services:
pg:
image: postgres:alpine
restart: always
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
my:
image: mariadb:latest
restart: always
environment:
MYSQL_DATABASE: test
MYSQL_USER: test
MYSQL_PASSWORD: test
MARIADB_ROOT_PASSWORD: root
ports:
- 3306:3306
adminer:
image: adminer
restart: always
ports:
- 8080:8080

14
go.mod
View File

@ -1,5 +1,15 @@
module forge.redroom.link/yves/sqldb
go 1.15
go 1.17
require github.com/lib/pq v1.9.0
require (
github.com/go-sql-driver/mysql v1.7.1
github.com/lib/pq v1.10.4
github.com/rs/zerolog v1.31.0
)
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
golang.org/x/sys v0.12.0 // indirect
)

21
go.sum
View File

@ -1,2 +1,19 @@
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

14
insert.json Normal file
View File

@ -0,0 +1,14 @@
{
"addcolumn": null,
"description": "tata",
"enddate": null,
"floatvalue": null,
"id": "1",
"intvalue": null,
"latitude": null,
"longitude": null,
"name": "toto",
"price": null,
"startdate": null,
"testtype_id": null
}

225
my_test.go Executable file
View File

@ -0,0 +1,225 @@
package sqldb
import (
"encoding/json"
"fmt"
"os"
"testing"
)
func TestMyCreateTable(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
byteValue, _ := os.ReadFile("test_table.json")
var jsonSource TableInfo
json.Unmarshal([]byte(byteValue), &jsonSource)
err := db.CreateTable(jsonSource)
if err != nil {
fmt.Println(err.Error())
}
sch, err := db.Table("test").GetSchema()
if err != nil {
fmt.Println(err.Error())
}
if len(sch.Columns) == 0 {
t.Errorf("Create table failed")
}
}
func TestMyAddColumn(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
old, err := db.Table("test").GetSchema()
if err != nil {
fmt.Println(err.Error())
}
db.Table("test").AddColumn("addcolumn", "integer", "comment")
new, err := db.Table("test").GetSchema()
if err != nil {
fmt.Println(err.Error())
}
if len(old.Columns) == len(new.Columns) {
t.Errorf("Column already exist")
}
}
func TestMyInsert(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
vl := make(AssRow)
vl["name"] = "toto"
vl["description"] = "tata"
vl["longitude"] = 1.38
vl["enddate"] = "2022-09-01"
vl["boolvalue"] = "true"
old, err := db.Table("test").GetAssociativeArray([]string{"*"}, "", []string{}, "")
if err != nil {
fmt.Println(err.Error())
}
jsonStringOld, _ := json.Marshal(old)
fmt.Println(string(jsonStringOld))
db.Table("test").UpdateOrInsert(vl)
new, err := db.Table("test").GetAssociativeArray([]string{"*"}, "", []string{}, "")
if err != nil {
fmt.Println(err.Error())
}
jsonStringNew, _ := json.Marshal(new)
fmt.Println(string(jsonStringNew))
if len(jsonStringOld) == len(jsonStringNew) {
t.Errorf("Error row not created")
}
var result map[string]interface{}
byteValue, _ := os.ReadFile("insert.json")
json.Unmarshal(byteValue, &result)
}
func TestMyUpdate(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
vl := make(AssRow)
vl["id"] = 1
vl["name"] = "titi"
vl["description"] = "toto"
old, err := db.Table("test").GetAssociativeArray([]string{"*"}, "", []string{}, "")
if err != nil {
fmt.Println(err.Error())
}
jsonStringOld, _ := json.Marshal(old)
fmt.Println(string(jsonStringOld))
db.Table("test").UpdateOrInsert(vl)
new, err := db.Table("test").GetAssociativeArray([]string{"*"}, "", []string{}, "")
if err != nil {
fmt.Println(err.Error())
}
jsonStringNew, _ := json.Marshal(new)
fmt.Println(string(jsonStringNew))
if string(jsonStringOld) == string(jsonStringNew) {
t.Errorf("Error row not updated")
}
}
func TestMyDelete(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
vl := make(AssRow)
vl["id"] = 1
old, err := db.Table("test").GetAssociativeArray([]string{"*"}, "", []string{}, "")
if err != nil {
fmt.Println(err.Error())
}
jsonStringOld, _ := json.Marshal(old)
fmt.Println(string(jsonStringOld))
db.Table("test").Delete(vl)
new, err := db.Table("test").GetAssociativeArray([]string{"*"}, "", []string{}, "")
if err != nil {
fmt.Println(err.Error())
}
jsonStringNew, _ := json.Marshal(new)
fmt.Println(string(jsonStringNew))
if len(jsonStringOld) == len(jsonStringNew) {
t.Errorf("Error row not deleted")
}
}
func TestMyDeleteColumn(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
old, err := db.Table("test").GetSchema()
if err != nil {
fmt.Println(err.Error())
}
db.Table("test").DeleteColumn("addcolumn")
new, err := db.Table("test").GetSchema()
if err != nil {
fmt.Println(err.Error())
}
if len(old.Columns) == len(new.Columns) {
t.Errorf("Error column not deleted")
}
}
func TestMyDeleteTable(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
db.Table("test").DeleteTable()
}
func TestMyImportSchema(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
db.ImportSchema("pfn.json")
}
func TestMyClearImportSchema(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
db.ClearImportSchema("pfn.json")
}
func TestMyGetSchema(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true?charset=utf8mb4&collation=utf8mb4_unicode_ci")
defer db.Close()
data, err := db.GetSchema()
if err != nil {
fmt.Println(err.Error())
}
val, _ := json.Marshal(data)
fmt.Println(string(val))
}
func TestMySaveSchema(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
err := db.SaveSchema("schema.json")
if err != nil {
fmt.Println(err.Error())
}
}
func TestMyGenerateTemplate(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
err := db.GenerateSchemaTemplate("plantuml.tmpl", "schema.puml")
if err != nil {
fmt.Println(err.Error())
}
}
func TestMyGenerateTableTemplate(t *testing.T) {
db := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test?parseTime=true")
defer db.Close()
err := db.GenerateTableTemplates("table.tmpl", "gen", "html")
if err != nil {
fmt.Println(err.Error())
}
}

68
pfn.json Normal file
View File

@ -0,0 +1,68 @@
[
{
"name":"computer",
"columns":
{
"id":"integer",
"name":"varchar(255)|comment",
"description":"varchar(1000)",
"os":"varchar(255)"
}
},
{
"name":"software",
"columns":
{
"id":"integer",
"name":"varchar(255)|comment",
"description":"varchar(1000)",
"licenseend":"date",
"support":"varchar(255)",
"company":"varchar(255)"
}
},
{
"name":"software_computer",
"columns":
{
"id":"integer",
"computer_id":"integer",
"software_id":"integer"
}
},
{
"name":"person",
"columns":
{
"id":"integer",
"name":"varchar(255)",
"firstname":"varchar(255)",
"email":"varchar(255)",
"mobile":"varchar(255)"
}
},
{
"name":"entity",
"columns":
{
"id":"integer",
"name":"varchar(255)",
"type":"varchar(255)",
"startdate":"date",
"enddate":"date"
}
},
{
"name":"timetracking",
"columns":
{
"id":"integer",
"day":"date",
"morning_entity_id":"integer",
"afternoon_entity_id":"integer",
"person_id":"integer",
"comment":"varchar(255)"
}
}
]

323
pg.go
View File

@ -1,323 +0,0 @@
package sqldb
import (
"database/sql"
"fmt"
"log"
"strconv"
"strings"
"github.com/lib/pq"
)
type Db struct {
Driver string
Url string
conn *sql.DB
}
// AssRow : associative row type
type AssRow map[string]interface{}
// Select Result
type Rows []AssRow
// Table is a table structure description
type TableInfo struct {
Name string `json:"name"`
Columns map[string]string `json:"columns"`
db *Db
}
// Open the database
func Open(driver string, url string) *Db {
var database Db
var err error
database.Driver = driver
database.Url = url
database.conn, err = sql.Open(driver, url)
if err != nil {
log.Println(err)
}
return &database
}
// Close the database connection
func (db *Db) Close() {
db.conn.Close()
}
func (db *Db) Table(name string) *TableInfo {
var ti TableInfo
ti.Name = name
ti.db = db
return &ti
}
// GetAssociativeArray : Provide results as an associative array
func (t *TableInfo) GetAssociativeArray(columns []string, restriction string, sortkeys []string, dir string) ([]AssRow, error) {
return t.db.QueryAssociativeArray(t.buildSelect("", columns, restriction, sortkeys, dir))
}
// QueryAssociativeArray : Provide results as an associative array
func (db *Db) QueryAssociativeArray(query string) (Rows, error) {
rows, err := db.conn.Query(query)
if err != nil {
log.Println(err)
log.Println(query)
return nil, err
}
defer rows.Close()
var results Rows
cols, err := rows.Columns()
if err != nil {
log.Println(err)
log.Println(query)
return nil, err
}
for rows.Next() {
// Create a slice of interface{}'s to represent each column,
// and a second slice to contain pointers to each item in the columns slice.
columns := make([]interface{}, len(cols))
columnPointers := make([]interface{}, len(cols))
for i := range columns {
columnPointers[i] = &columns[i]
}
// Scan the result into the column pointers...
if err := rows.Scan(columnPointers...); err != nil {
}
// Create our map, and retrieve the value for each column from the pointers slice,
// storing it in the map with the name of the column as the key.
m := make(AssRow)
for i, colName := range cols {
val := columnPointers[i].(*interface{})
m[colName] = fmt.Sprintf("%v", *val)
}
results = append(results, m)
}
return results, nil
}
// GetSchema : Provide results as an associative array
func (t *TableInfo) GetSchema() (*TableInfo, error) {
var ti TableInfo
ti.Name = t.Name
ti.db = t.db
cols, err := t.db.QueryAssociativeArray("SELECT column_name :: varchar as name, REPLACE(REPLACE(data_type,'character varying','varchar'),'character','char') || COALESCE('(' || character_maximum_length || ')', '') as type from INFORMATION_SCHEMA.COLUMNS where table_name ='" + t.Name + "';")
if err != nil {
log.Println(err)
return nil, err
}
ti.Columns = make(map[string]string)
for _, row := range cols {
var name, rowtype string
for key, element := range row {
if key == "name" {
name = fmt.Sprintf("%v", element)
}
if key == "type" {
rowtype = fmt.Sprintf("%v", element)
}
}
ti.Columns[name] = rowtype
}
return &ti, nil
}
func (db *Db) ListTables() (Rows, error) {
return db.QueryAssociativeArray("SELECT table_name :: varchar FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;")
}
func (db *Db) CreateTable(t TableInfo) error {
t.db = db
query := "create table " + t.Name + " ( "
columns := ""
for name, rowtype := range t.Columns {
if fmt.Sprintf("%v", name) == "id" {
columns += fmt.Sprintf("%v", name) + " " + "SERIAL PRIMARY KEY,"
} else {
columns += fmt.Sprintf("%v", name) + " " + fmt.Sprintf("%v", rowtype)
columns += ","
}
}
query += columns
query = query[:len(query)-1] + " )"
_, err := t.db.conn.Query(query)
if err != nil {
log.Println(err.Error())
return err
}
return nil
}
func (t *TableInfo) DeleteTable() error {
query := "drop table " + t.Name
_, err := t.db.conn.Query(query)
if err != nil {
log.Println(err.Error())
return err
}
query = "drop sequence if exists sq_" + t.Name
_, err = t.db.conn.Query(query)
if err != nil {
log.Println(err.Error())
return err
}
return nil
}
func (t *TableInfo) AddColumn(name string, sqltype string) error {
query := "alter table " + t.Name + " add " + name + " " + sqltype
rows, err := t.db.conn.Query(query)
if err != nil {
log.Println(err)
return err
}
defer rows.Close()
return nil
}
func (t *TableInfo) DeleteColumn(name string) error {
query := "alter table " + t.Name + " drop " + name
rows, err := t.db.conn.Query(query)
if err != nil {
log.Println(err)
return err
}
defer rows.Close()
return nil
}
func (db *Db) ListSequences() (Rows, error) {
return db.QueryAssociativeArray("SELECT sequence_name :: varchar FROM information_schema.sequences WHERE sequence_schema = 'public' ORDER BY sequence_name;")
}
func (t *TableInfo) buildSelect(key string, columns []string, restriction string, sortkeys []string, dir ...string) string {
if key != "" {
columns = append(columns, key)
}
query := "select " + strings.Join(columns, ",") + " from " + t.Name
if restriction != "" {
query += " where " + restriction
}
if len(sortkeys) > 0 {
query += " order by " + strings.Join(sortkeys, ",")
}
if len(dir) > 0 {
query += " " + dir[0]
}
return query
}
func (t *TableInfo) Insert(record AssRow) (int, error) {
columns := ""
values := ""
t, err := t.GetSchema()
if err != nil {
log.Println(err)
return -1, err
}
var id int
for key, element := range record {
if strings.Contains(t.Columns[key], "char") || strings.Contains(t.Columns[key], "date") {
columns += key + ","
values += fmt.Sprint(pq.QuoteLiteral(fmt.Sprintf("%v", element))) + ","
} else {
columns += key + ","
values += fmt.Sprintf("%v", element) + ","
}
}
t.db.conn.QueryRow("INSERT INTO " + t.Name + "(" + removeLastChar(columns) + ") VALUES (" + removeLastChar(values) + ") RETURNING id").Scan(&id)
return id, nil
}
func (t *TableInfo) Update(record AssRow) error {
t, err := t.GetSchema()
if err != nil {
log.Println(err)
return err
}
id := ""
stack := ""
for key, element := range record {
if strings.Contains(t.Columns[key], "char") || strings.Contains(t.Columns[key], "date") {
stack = stack + " " + key + " = " + pq.QuoteLiteral(fmt.Sprintf("%v", element)) + ","
} else {
if key == "id" {
id = fmt.Sprintf("%v", element)
} else {
stack = stack + " " + key + " = " + fmt.Sprintf("%v", element) + ","
}
}
}
stack = removeLastChar(stack)
query := ("UPDATE " + t.Name + " SET " + stack + " WHERE id = " + id)
rows, err := t.db.conn.Query(query)
if err != nil {
log.Println(query)
log.Println(err)
return err
}
defer rows.Close()
return nil
}
func (t *TableInfo) Delete(record AssRow) error {
id := ""
values := ""
for key, element := range record {
if key == "id" {
values += fmt.Sprintf("%v", element) + ","
id = removeLastChar(values)
}
}
query := ("DELETE FROM " + t.Name + " WHERE id = " + id)
rows, err := t.db.conn.Query(query)
if err != nil {
log.Println(query)
log.Println(err)
return err
}
defer rows.Close()
return nil
}
func (t *TableInfo) UpdateOrInsert(record AssRow) (int, error) {
id := -1
for key, element := range record {
if key == "id" {
sid := fmt.Sprintf("%v", element)
id, _ = strconv.Atoi(sid)
}
}
if id == -1 {
return t.Insert(record)
} else {
t.Update(record)
return id, nil
}
}
func removeLastChar(s string) string {
r := []rune(s)
return string(r[:len(r)-1])
}

View File

@ -3,27 +3,20 @@ package sqldb
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"
)
func TestCreateTable(t *testing.T) {
func TestPgCreateTable(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
jsonFile, err := os.Open("test_table.json")
if err != nil {
fmt.Println(err)
}
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
byteValue, _ := os.ReadFile("testtype_table.json")
var jsonSource TableInfo
json.Unmarshal([]byte(byteValue), &jsonSource)
err = db.CreateTable(jsonSource)
err := db.CreateTable(jsonSource)
if err != nil {
fmt.Println(err.Error())
}
@ -37,7 +30,7 @@ func TestCreateTable(t *testing.T) {
}
}
func TestAddColumn(t *testing.T) {
func TestPgAddColumn(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
@ -46,7 +39,7 @@ func TestAddColumn(t *testing.T) {
if err != nil {
fmt.Println(err.Error())
}
db.Table("test").AddColumn("addcolumn", "integer")
db.Table("test").AddColumn("addcolumn", "integer", "comment")
new, err := db.Table("test").GetSchema()
if err != nil {
fmt.Println(err.Error())
@ -57,7 +50,7 @@ func TestAddColumn(t *testing.T) {
}
}
func TestInsert(t *testing.T) {
func TestPgInsert(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
@ -65,6 +58,9 @@ func TestInsert(t *testing.T) {
vl := make(AssRow)
vl["name"] = "toto"
vl["description"] = "tata"
vl["longitude"] = 1.38
vl["enddate"] = "2022-09-01"
vl["boolvalue"] = "true"
old, err := db.Table("test").GetAssociativeArray([]string{"*"}, "", []string{}, "")
if err != nil {
@ -85,9 +81,12 @@ func TestInsert(t *testing.T) {
if len(jsonStringOld) == len(jsonStringNew) {
t.Errorf("Error row not created")
}
var result map[string]interface{}
byteValue, _ := os.ReadFile("insert.json")
json.Unmarshal(byteValue, &result)
}
func TestUpdate(t *testing.T) {
func TestPgUpdate(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
@ -119,7 +118,7 @@ func TestUpdate(t *testing.T) {
}
func TestDelete(t *testing.T) {
func TestPgDelete(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
@ -148,7 +147,7 @@ func TestDelete(t *testing.T) {
}
}
func TestDeleteColumn(t *testing.T) {
func TestPgDeleteColumn(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
@ -168,17 +167,58 @@ func TestDeleteColumn(t *testing.T) {
}
}
func TestDeleteTable(t *testing.T) {
func TestPgDeleteTable(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
db.Table("test").DeleteTable()
tbl, err := db.Table("test").GetSchema()
}
func TestPgImportSchema(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
db.ImportSchema("pfn.json")
}
func TestPgClearImportSchema(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
db.ClearImportSchema("pfn.json")
}
func TestPgGetSchema(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
data, err := db.GetSchema()
if err != nil {
fmt.Println(err.Error())
}
if len(tbl.Columns) != 0 {
t.Errorf("Delete table failed")
val, _ := json.Marshal(data)
fmt.Println(string(val))
}
func TestPgSaveSchema(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
err := db.SaveSchema("schema.json")
if err != nil {
fmt.Println(err.Error())
}
}
func TestPgGenerateTemplate(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
err := db.GenerateSchemaTemplate("plantuml.tmpl", "schema.puml")
if err != nil {
fmt.Println(err.Error())
}
}
func TestPgGenerateTableTemplate(t *testing.T) {
db := Open("postgres", "host=127.0.0.1 port=5432 user=test password=test dbname=test sslmode=disable")
defer db.Close()
err := db.GenerateTableTemplates("table.tmpl", "gen", "html")
if err != nil {
fmt.Println(err.Error())
}
}

14
plantuml.tmpl Normal file
View File

@ -0,0 +1,14 @@
@startuml
{{range .Tbl}}
entity {{.Name}} {
{{range $key, $value := .Columns}} {{$key}} : {{$value}}
{{end}}}
{{end}}
{{range .Lnk}}
{{.Source}} ||..|| {{.Destination}}
{{end}}
@enduml

1
schema.json Normal file
View File

@ -0,0 +1 @@
null

8
schema.puml Normal file
View File

@ -0,0 +1,8 @@
@startuml
@enduml

58
survey.json Normal file
View File

@ -0,0 +1,58 @@
[
{
"name": "survey",
"columns": {
"id": "integer",
"name": "varchar(255)",
"description": "varchar(1000)",
"start":"timestmp",
"stop":"timestmpestamp",
"published":"bool"
}
},
{
"name": "surveyquestion",
"columns": {
"id": "integer",
"survey_id": "integer",
"question_id": "integer"
}
},
{
"name": "chapter",
"columns": {
"id": "integer",
"name": "varchar(255)",
"order": "integer"
}
},
{
"name": "question",
"columns": {
"name": "varchar(255)",
"text": "varchar(1000)",
"type": "varchar(255)",
"options": "varchar(1000)",
"id": "integer",
"order": "integer",
"chapter_id": "integer"
}
},
{
"name": "authuser",
"columns": {
"survey_id": "integer",
"code": "varchar(1000)",
"id": "integer"
}
},
{
"name": "answer",
"columns": {
"question_id": "integer",
"code_id": "integer",
"value":"integer",
"text": "varchar(2000)"
}
}
]

3
table.tmpl Normal file
View File

@ -0,0 +1,3 @@
{{.Name}}
{{range $key, $value := .Columns}} {{$key}} : {{$value}}
{{end}}

View File

@ -3,15 +3,16 @@
"columns":
{
"id":"integer",
"name":"varchar(255)",
"name":"varchar(255)|comment",
"description":"varchar(1000)",
"startdate":"timestamp",
"enddate":"timestamp",
"latitude":"float",
"longitude":"float",
"startdate":"date",
"endtime":"timestamp",
"latitude":"float|map",
"longitude":"float|map",
"intvalue":"integer",
"floatvalue":"float",
"price":"money",
"testtype_id":"integer"
"price":"float",
"testtype_id":"integer",
"boolvalue":"boolean"
}
}

View File

@ -1,17 +1,10 @@
{
"name":"testtype",
"columns":[
"columns":
{
"name":"id",
"type":"integer"
},
{
"name":"name",
"type":"varchar(255)"
},
{
"name":"detail",
"type":"varchar(255)"
"id":"integer",
"name":"varchar(255)",
"detail":"varchar(255)"
}
]
}