Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions hexya/models/db_postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ var pgTypes = map[fieldtype.Type]string{
fieldtype.Selection: "character varying",
fieldtype.Many2One: "integer",
fieldtype.One2One: "integer",
fieldtype.JSON: "jsonb",
}

var pgDefaultValues = map[fieldtype.Type]string{
Expand All @@ -68,6 +69,7 @@ var pgDefaultValues = map[fieldtype.Type]string{
fieldtype.HTML: "''",
fieldtype.Binary: "''",
fieldtype.Selection: "''",
fieldtype.JSON: "'{}'",
}

// operatorSQL returns the sql string and placeholders for the given DomainOperator
Expand Down Expand Up @@ -130,6 +132,10 @@ func (d *postgresAdapter) fieldIsNotNull(fi *Field) bool {
}
return false
}
switch fi.fieldType {
case fieldtype.JSON:
return false
}
return true
}

Expand Down
63 changes: 63 additions & 0 deletions hexya/models/fields_defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,69 @@ func (df DateTimeField) DeclareField(fc *FieldsCollection, name string) {
fc.add(fInfo)
}

// A JSONField is a field for storing jsonb.

type JSONField struct {
JSON string
String string
Help string
Stored bool
Required bool
Unique bool
Index bool
Compute Methoder
Depends []string
Related string
GroupOperator string
NoCopy bool
GoType interface{}
Translate bool
OnChange Methoder
Constraint Methoder
Inverse Methoder
Default func(Environment, FieldMap) interface{}
}

// DeclareField adds this text field to the given FieldsCollection with the given name.
func (jsonb JSONField) DeclareField(fc *FieldsCollection, name string) {
typ := reflect.TypeOf(*new(types.JSONText))
if jsonb.GoType != nil {
typ = reflect.TypeOf(jsonb.GoType).Elem()
}
structField := reflect.StructField{
Name: name,
Type: typ,
}
fieldType := fieldtype.JSON
json, str := getJSONAndString(name, fieldType, jsonb.JSON, jsonb.String)
compute, inverse, onchange, constraint := getFuncNames(jsonb.Compute, jsonb.Inverse, jsonb.OnChange, jsonb.Constraint)
fInfo := &Field{
model: fc.model,
acl: security.NewAccessControlList(),
name: name,
json: json,
description: str,
help: jsonb.Help,
stored: jsonb.Stored,
required: jsonb.Required,
unique: jsonb.Unique,
index: jsonb.Index,
compute: compute,
inverse: inverse,
depends: jsonb.Depends,
relatedPath: jsonb.Related,
groupOperator: strutils.GetDefaultString(jsonb.GroupOperator, "sum"),
noCopy: jsonb.NoCopy,
structField: structField,
fieldType: fieldType,
defaultFunc: jsonb.Default,
translate: jsonb.Translate,
onChange: onchange,
constraint: constraint,
}
fc.add(fInfo)
}

// A FloatField is a field for storing decimal numbers.
type FloatField struct {
JSON string
Expand Down
4 changes: 4 additions & 0 deletions hexya/models/fieldtype/fieldtype.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package fieldtype
import (
"reflect"

"github.com/hexya-erp/hexya/hexya/models/types"
"github.com/hexya-erp/hexya/hexya/models/types/dates"
)

Expand All @@ -31,6 +32,7 @@ const (
Reference Type = "reference"
Selection Type = "selection"
Text Type = "text"
JSON Type = "json"
)

// IsRelationType returns true if this type is a relation.
Expand Down Expand Up @@ -81,6 +83,8 @@ func (t Type) DefaultGoType() reflect.Type {
return reflect.TypeOf(*new(dates.Date))
case DateTime:
return reflect.TypeOf(*new(dates.DateTime))
case JSON:
return reflect.TypeOf(*new(types.JSONText))
case Float:
return reflect.TypeOf(*new(float64))
case Integer, Many2One, One2One, Rev2One:
Expand Down
118 changes: 118 additions & 0 deletions hexya/models/types/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package types

import (
"database/sql/driver"
"encoding/json"
"errors"
)

// Source: https://github.com/jmoiron/sqlx/blob/master/types/types.go
//
// For HEXYA: In db.sanitizeQuery() function, call to sqlx.Rebind(sqlx.BindType(db.DriverName()), q)
// causes invalid SQL statement with json.RawMessage type. So, I change JSONText to string type

//type JSONText json.RawMessage
type JSONText string

var emptyJSON = JSONText("{}")

//// MarshalJSON returns the *j as the JSON encoding of j.
//func (j JSONText) MarshalJSON() ([]byte, error) {
// if len(j) == 0 {
// return emptyJSON, nil
// }
// return j, nil
//}

//// UnmarshalJSON sets *j to a copy of data
//func (j *JSONText) UnmarshalJSON(data []byte) error {
// if j == nil {
// return errors.New("JSONText: UnmarshalJSON on nil pointer")
// }
// *j = append((*j)[0:0], data...)
// return nil
//}

// Value returns j as a value. This does a validating unmarshal into another
// RawMessage. If j is invalid json, it returns an error.
func (j JSONText) Value() (driver.Value, error) {
var m json.RawMessage
var err = j.Unmarshal(&m)
if err != nil {
return "{}", err
}
return string(j), nil
}

// Scan stores the src in *j. No validation is done.
func (j *JSONText) Scan(src interface{}) error {
if src == nil {
*j = emptyJSON
} else {
*j = JSONText(string(src.([]uint8)))
}
return nil
}

// Unmarshal unmarshal's the json in j to v, as in json.Unmarshal.
func (j *JSONText) Unmarshal(v interface{}) error {
if len(*j) == 0 {
*j = emptyJSON
}
return json.Unmarshal([]byte(*j), v)
}

// String supports pretty printing for JSONText types.
func (j JSONText) String() string {
return string(j)
}

// NullJSONText represents a JSONText that may be null.
// NullJSONText implements the scanner interface so
// it can be used as a scan destination, similar to NullString.
type NullJSONText struct {
JSONText
Valid bool // Valid is true if JSONText is not NULL
}

// Scan implements the Scanner interface.
func (n *NullJSONText) Scan(value interface{}) error {
if value == nil {
n.JSONText, n.Valid = emptyJSON, false
return nil
}
n.Valid = true
return n.JSONText.Scan(value)
}

// Value implements the driver Valuer interface.
func (n NullJSONText) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.JSONText.Value()
}

// BitBool is an implementation of a bool for the MySQL type BIT(1).
// This type allows you to avoid wasting an entire byte for MySQL's boolean type TINYINT.
type BitBool bool

// Value implements the driver.Valuer interface,
// and turns the BitBool into a bitfield (BIT(1)) for MySQL storage.
func (b BitBool) Value() (driver.Value, error) {
if b {
return []byte{1}, nil
}
return []byte{0}, nil
}

// Scan implements the sql.Scanner interface,
// and turns the bitfield incoming from MySQL into a BitBool
func (b *BitBool) Scan(src interface{}) error {
v, ok := src.([]byte)
if !ok {
return errors.New("bad []byte type assertion")
}
*b = v[0] == 1
return nil
}