Documentation
¶
Overview ¶
Tdb provides the Parse and Unmarshal functions for reading []byte slices of text in Tdb “Text DataBase” format, and the Tdb.Write and Marshal functions for writing to Tdb format.
The Parse function creates a Tdb object which stores values as type `any`, so is useful for applications that need to process generic Tdb files. Tdb data is written in Tdb format using the Tdb.Write method. However, if the Tdb file format is known, then it is best to use Marshal and Unmarshal since these use the appropriate concrete types (`bool`, `int`, `string`, and so on).
To use the Marshal and Unmarshal functions you must provide a populated (for Marshal) or unpopulated (for Unmarshal) struct. This outer struct represents a text database. The outer struct must contain one or more public (inner) fields, each of type slice of struct. Each inner field represents a database table, and each record is represented by an inner field struct.
Tdb format ¶
Tdb “Text DataBase” format is a plain text human readable typed database storage format.
Tdb provides a superior alternative to CSV. In particular, Tdb tables are named and Tdb fields are strictly typed. Also, there is a clear distinction between field names and data values, and strings respect whitespace (including newlines) and have no problems with commas, quotes, etc. Perhaps best of all, a single Tdb file may contain one—or more—tables.
See README.md at https://github.com/mark-summerfield/tdb-go for more about the Tdb format.
Using the tdb package ¶
Import using:
import tdb "github.com/mark-summerfield/tdb-go"
Types:
| Tdb Type | Go Types | |----------|----------------------------| | bool | bool | | bytes | []byte | | date | time.Time | | datetime | time.Time | | int | int uint int32 uint32 etc. | | real | float64 float32 | | str | string |
Note that for nullable types (e.g., `bool?`, `str?`, etc.) the corresponding Go type must be a pointer (e.g., `*bool`, `*string`, etc.).
The Marshal and Unmarshal examples use these structs:
type classicDatabase struct { Employees []Employee `tdb:"emp"` Departments []Department `tdb:"dept"` } type Employee struct { EID int `tdb:"empno"` Name string `tdb:"ename"` Job string `tdb:"job"` ManagerID *int `tdb:"mgr"` // The boss doesn't have a mgr HireDate time.Time `tdb:"hiredate:date"` Salary float64 `tdb:"sal"` Commission *float64 `tdb:"comm"` // Most don't get commission DeptID int `tdb:"deptno"` } type Department struct { DID int `tdb:"deptno"` Name string `tdb:"dname"` Location string `tdb:"loc"` }
Although struct tags are used extensively here, they are only actually required for two purposes. A tag is needed if a Tdb file's table or field name is different from the corresponding struct name. And a tag is needed for time.Time fields if the field is a Tdb `date` field (since the default is `datetime`). For example, see `db1_test.go` and `csv_test.go` for structs which work fine despite having few tags.
The order of tables in a Tdb file in relation to the outer struct doesn't matter. However, the order of fields within a table must match between the Tdb file's table definition and the corresponding struct.
Naturally, you can use any structs you like that meet tdb's minimum requirements.
Index ¶
- Constants
- Variables
- func Escape(s string) string
- func Marshal(db any) ([]byte, error)
- func MarshalDecimals(db any, decimals int) ([]byte, error)
- func Unescape(s string) string
- func Unmarshal(data []byte, db any) error
- type FieldKind
- type MetaFieldType
- type MetaTableType
- type Record
- type Table
- type Tdb
Examples ¶
Constants ¶
const ( DateFormat = "2006-01-02" DateTimeFormat = "2006-01-02T15:04:05" )
Variables ¶
var Version string // This tdb package's version.
Functions ¶
func Escape ¶
Escape returns an XML-escaped string, i.e., where runes are replaced as follows: & → &, < → <, > → >. See also Unescape.
func Marshal ¶
Marshal converts the given struct of slices of structs to a string (as raw UTF-8-encoded bytes) in Tdb format if possible.
Each tablename is taken from the outer struct's fieldname, but this can be overridden using a tag, e.g., `tdb:"MyTableName"`. For time.Time fields use a tag of either `tdb:"date"` or `tdb:"datetime"` to specify the Tdb field type; for all other types, the Tdb type is inferred. However, if fieldnames in the Tdb text are to be different from the struct fieldnames, use tags, with the required name, e.g., `tdb:"MyFieldName"`, and for dates and datetimes with the type too, e.g., `tdb:"MyDateField:date"`, etc.
See also Tdb.Write and MarshalDecimals and Unmarshal.
Example ¶
package main import ( "fmt" _ "embed" tdb "github.com/mark-summerfield/tdb-go" "time" ) func main() { db := classicDatabase{ Employees: []Employee{ {7844, "TURNER", "SALESMAN", nil, date(1981, time.September, 8), 1500.0, nil, 30}, {7876, "ADAMS", "CLERK", nil, date(1983, time.January, 12), 1100.0, nil, 20}, {7839, "KING", "PRESIDENT", nil, date(1981, time.November, 17), 5000.0, nil, 10}, {7902, "FORD", "ANALYST", nil, date(1981, time.December, 3), 3000.0, nil, 20}, }, Departments: []Department{ {10, "ACCOUNTING", "NEW YORK"}, {20, "RESEARCH", "DALLAS"}, {30, "SALES", "CHICAGO"}, }, } c0 := 0.0 db.Employees[0].Commission = &c0 m0 := 7698 db.Employees[0].ManagerID = &m0 m1 := 7788 db.Employees[1].ManagerID = &m1 m3 := 7566 db.Employees[3].ManagerID = &m3 raw, err := tdb.Marshal(db) if err != nil { panic(err) } fmt.Println(string(raw)) } type classicDatabase struct { Employees []Employee `tdb:"emp"` Departments []Department `tdb:"dept"` } type Employee struct { EID int `tdb:"empno"` Name string `tdb:"ename"` Job string `tdb:"job"` ManagerID *int `tdb:"mgr"` HireDate time.Time `tdb:"hiredate:date"` Salary float64 `tdb:"sal"` Commission *float64 `tdb:"comm"` DeptID int `tdb:"deptno"` } type Department struct { DID int `tdb:"deptno"` Name string `tdb:"dname"` Location string `tdb:"loc"` } func date(year int, month time.Month, day int) time.Time { return time.Date(year, month, day, 0, 0, 0, 0, time.UTC) }
Output: [emp empno int ename str job str mgr int? hiredate date sal real comm real? deptno int % 7844 <TURNER> <SALESMAN> 7698 1981-09-08 1500 0 30 7876 <ADAMS> <CLERK> 7788 1983-01-12 1100 ? 20 7839 <KING> <PRESIDENT> ? 1981-11-17 5000 ? 10 7902 <FORD> <ANALYST> 7566 1981-12-03 3000 ? 20 ] [dept deptno int dname str loc str % 10 <ACCOUNTING> <NEW YORK> 20 <RESEARCH> <DALLAS> 30 <SALES> <CHICAGO> ]
func MarshalDecimals ¶ added in v0.4.0
MarshalDecimals is a refinement of the Marshal function.
By default for real numbers the Marshal function outputs them using the fewest number of decimal digits necessary. In particular this means that numbers whose fractional part is 0 are output like ints (e.g., 2.0 → 2).
To control decimal output use this function. Pass a decimals value of 1-19 to use exactly that number of decimal digits; any other value means use the minimum number of decimal digits necessary (which may be none for numbers whose fractional part is 0).
func Unescape ¶
Unescape accepts an XML-escaped string and returns a plain text string with no escapes, i.e., where substrings are replaced with runes as follows: & → &, < → <, > → >. See also Escape.
func Unmarshal ¶
Unmarshal reads the data from the given string (as raw UTF-8-encoded bytes) into a (pointer to a) database struct.
See also Parse and Marshal and MarshalDecimals.
Example ¶
package main import ( "fmt" _ "embed" tdb "github.com/mark-summerfield/tdb-go" "time" ) func main() { tdbText := `[emp empno int ename str job str mgr int? hiredate date sal real comm real? deptno int % 7844 <TURNER> <SALESMAN> 7698 1981-09-08 1500 0 30 7876 <ADAMS> <CLERK> 7788 1983-01-12 1100 ? 20 7839 <KING> <PRESIDENT> ? 1981-11-17 5000 ? 10 7902 <FORD> <ANALYST> 7566 1981-12-03 3000 ? 20 ] [dept deptno int dname str loc str % 10 <ACCOUNTING> <NEW YORK> 20 <RESEARCH> <DALLAS> 30 <SALES> <CHICAGO> ]` db := classicDatabase{} if err := tdb.Unmarshal([]byte(tdbText), &db); err != nil { panic(err) } fmt.Printf("%d Employees\n", len(db.Employees)) fmt.Printf("%d Departments\n", len(db.Departments)) president := db.Employees[2] managerID := -1 if president.ManagerID != nil { managerID = *president.ManagerID } commission := 0.0 if president.Commission != nil { commission = *president.Commission } fmt.Printf("%d %q %q %d %s %g %g %d\n", president.EID, president.Name, president.Job, managerID, president.HireDate.Format(tdb.DateFormat), president.Salary, commission, president.DeptID) research := db.Departments[1] fmt.Printf("%d %q %q\n", research.DID, research.Name, research.Location) } type classicDatabase struct { Employees []Employee `tdb:"emp"` Departments []Department `tdb:"dept"` } type Employee struct { EID int `tdb:"empno"` Name string `tdb:"ename"` Job string `tdb:"job"` ManagerID *int `tdb:"mgr"` HireDate time.Time `tdb:"hiredate:date"` Salary float64 `tdb:"sal"` Commission *float64 `tdb:"comm"` DeptID int `tdb:"deptno"` } type Department struct { DID int `tdb:"deptno"` Name string `tdb:"dname"` Location string `tdb:"loc"` }
Output: 4 Employees 3 Departments 7839 "KING" "PRESIDENT" -1 1981-11-17 5000 0 10 20 "RESEARCH" "DALLAS"
Types ¶
type MetaFieldType ¶ added in v0.8.0
type MetaTableType ¶ added in v0.8.0
type MetaTableType struct { Name string Fields []*MetaFieldType }
MetaTableType holds the name of a table and a slice of its fields (names and kinds)
func (*MetaTableType) AddField ¶ added in v0.8.0
func (me *MetaTableType) AddField(fieldName, typeName string) bool
func (*MetaTableType) Field ¶ added in v0.8.0
func (me *MetaTableType) Field(index int) *MetaFieldType
func (MetaTableType) Len ¶ added in v0.8.0
func (me MetaTableType) Len() int
func (MetaTableType) String ¶ added in v0.8.0
func (me MetaTableType) String() string
type Table ¶ added in v0.8.0
type Table struct { MetaTableType // table name and field names and kinds Records []Record }
type Tdb ¶ added in v0.8.0
type Tdb struct { TableNames []string // order of reading & writing from/to file Tables map[string]*Table // key is tablename }
func Parse ¶ added in v0.8.0
Parse reads the data from the given string (as raw UTF-8-encoded bytes) and returns a Tdb object that holds all the tables and values (the values as “any“s).
See also Tdb.Write and Marshal and MarshalDecimals.
func (*Tdb) Write ¶ added in v0.8.0
Write writes the Tdb's tables and values to the given writer in Tdb format.
See also [WriteDecimals] and Parse.
func (*Tdb) WriteDecimals ¶ added in v0.8.0
WriteDecimals is a refinement of the [Write] method that writes the Tdb's tables and values to the given writer in Tdb format.
By default for real numbers the [Write] method outputs them using the fewest number of decimal digits necessary. In particular this means that numbers whose fractional part is 0 are output like ints (e.g., 2.0 → 2).
To control decimal output use this function. Pass a decimals value of 1-19 to use exactly that number of decimal digits; any other value means use the minimum number of decimal digits necessary (which may be none for numbers whose fractional part is 0).
See also [WriteDecimals] and Parse.