feat: try to write orm
This commit is contained in:
commit
45bc30b2f1
156
.gitignore
vendored
Normal file
156
.gitignore
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Linux template
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### Example user template template
|
||||
### Example user template
|
||||
|
||||
# IntelliJ project files
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
### Windows template
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
10
d3/main.go
Normal file
10
d3/main.go
Normal file
@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import "flag"
|
||||
|
||||
var debug = flag.Bool("debug", false, "dont delete temporary files")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
}
|
||||
46
d3/parser/mod.go
Normal file
46
d3/parser/mod.go
Normal file
@ -0,0 +1,46 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
doubleSlash = []byte("//")
|
||||
moduleStr = []byte("module")
|
||||
)
|
||||
|
||||
func ModulePath(mod []byte) string {
|
||||
for len(mod) > 0 {
|
||||
line := mod
|
||||
mod = nil
|
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 {
|
||||
line, mod = line[:i], line[i+1:]
|
||||
}
|
||||
if i := bytes.Index(line, doubleSlash); i >= 0 {
|
||||
line = line[:i]
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if !bytes.HasPrefix(line, moduleStr) {
|
||||
continue
|
||||
}
|
||||
line = line[len(moduleStr):]
|
||||
n := len(line)
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == n || len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0] == '"' || line[0] == '`' {
|
||||
p, err := strconv.Unquote(string(line))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
return string(line)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
11
go.mod
Normal file
11
go.mod
Normal file
@ -0,0 +1,11 @@
|
||||
module git.influ.su/artmares/d3orm
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
17
go.sum
Normal file
17
go.sum
Normal file
@ -0,0 +1,17 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
58
orm/entity/meta.go
Normal file
58
orm/entity/meta.go
Normal file
@ -0,0 +1,58 @@
|
||||
package entity
|
||||
|
||||
import "reflect"
|
||||
|
||||
type PkStrategy int
|
||||
|
||||
const (
|
||||
_ PkStrategy = iota
|
||||
Auto
|
||||
Manual
|
||||
)
|
||||
|
||||
type (
|
||||
Index struct {
|
||||
Name string
|
||||
Columns []string
|
||||
Unique bool
|
||||
}
|
||||
InternalTools struct {
|
||||
ExtractField FieldExtractor
|
||||
SetFieldValue FieldSetter
|
||||
NewInstance Instantiator
|
||||
Copy Copier
|
||||
CompareField FieldComparator
|
||||
}
|
||||
FieldExtractor func(e any, name string) (any, error)
|
||||
FieldSetter func(e any, name string, value any) error
|
||||
Instantiator func() any
|
||||
Copier func(src any, dst any)
|
||||
FieldComparator func(e1, e2 any, name string) bool
|
||||
)
|
||||
|
||||
type D3Entity interface {
|
||||
D3Token() MetaToken
|
||||
}
|
||||
|
||||
type MetaToken struct {
|
||||
Tools InternalTools
|
||||
Tpl any
|
||||
TableName string
|
||||
Indexes []Index
|
||||
}
|
||||
|
||||
type MetaInfo struct {
|
||||
Tpl any
|
||||
EntityName Name
|
||||
TableName string
|
||||
Indexes []Index
|
||||
|
||||
Relations map[string]Relation
|
||||
}
|
||||
|
||||
type FieldInfo struct {
|
||||
Name string
|
||||
DBAlias string
|
||||
FullDBAlias string
|
||||
AssociatedType reflect.Type
|
||||
}
|
||||
48
orm/entity/name.go
Normal file
48
orm/entity/name.go
Normal file
@ -0,0 +1,48 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Name string
|
||||
|
||||
func NameFromEntity(e any) Name {
|
||||
t := reflect.TypeOf(e)
|
||||
switch t.Kind() {
|
||||
case reflect.Ptr:
|
||||
return Name(fmt.Sprintf("%s/%s", t.Elem().PkgPath(), t.Elem().Name()))
|
||||
default:
|
||||
return Name(fmt.Sprintf("%s/%s", t.PkgPath(), t.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
func nameFromTag(tag string, parentName Name) Name {
|
||||
defined := Name(tag)
|
||||
if defined.IsShort() {
|
||||
return parentName.Combine(defined)
|
||||
}
|
||||
|
||||
return defined
|
||||
}
|
||||
|
||||
func (n Name) Short() string {
|
||||
path := strings.Split(string(n), "/")
|
||||
|
||||
return path[len(path)-1]
|
||||
}
|
||||
|
||||
func (n Name) IsShort() bool {
|
||||
return !strings.Contains(string(n), "/")
|
||||
}
|
||||
|
||||
func (n Name) Equal(name Name) bool {
|
||||
return n == name
|
||||
}
|
||||
|
||||
func (n Name) Combine(entity Name) Name {
|
||||
path := strings.Split(string(n), "/")
|
||||
|
||||
return Name(strings.Join(append(path[:len(path)-1], entity.Short()), "/"))
|
||||
}
|
||||
52
orm/entity/relation.go
Normal file
52
orm/entity/relation.go
Normal file
@ -0,0 +1,52 @@
|
||||
package entity
|
||||
|
||||
type DeleteStrategy int
|
||||
|
||||
const (
|
||||
_ DeleteStrategy = iota
|
||||
None
|
||||
Cascade
|
||||
Nullable
|
||||
)
|
||||
|
||||
func deleteStrategyFromAlias(alias string) DeleteStrategy {
|
||||
switch alias {
|
||||
case "cascade":
|
||||
return Cascade
|
||||
case "nullable":
|
||||
return Nullable
|
||||
default:
|
||||
return None
|
||||
}
|
||||
}
|
||||
|
||||
type RelationType int
|
||||
|
||||
const (
|
||||
_ RelationType = iota
|
||||
Lazy
|
||||
Eager
|
||||
SmartLazy
|
||||
)
|
||||
|
||||
func relationTypeFromAlias(alias string) RelationType {
|
||||
switch alias {
|
||||
case "lazy":
|
||||
return Lazy
|
||||
case "eager":
|
||||
return Eager
|
||||
default:
|
||||
return Lazy
|
||||
}
|
||||
}
|
||||
|
||||
type Relation interface {
|
||||
Type() RelationType
|
||||
DeleteStrategy() DeleteStrategy
|
||||
RelationWith() Name
|
||||
|
||||
Field() *FieldInfo
|
||||
|
||||
setField(f *FieldInfo)
|
||||
fillFromTag(tag *parsedTag, parent *MetaInfo)
|
||||
}
|
||||
138
orm/entity/tag.go
Normal file
138
orm/entity/tag.go
Normal file
@ -0,0 +1,138 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type property struct {
|
||||
name string
|
||||
value string
|
||||
subProperty map[string]property
|
||||
}
|
||||
|
||||
func (p *property) getSubPropVal(name string) string {
|
||||
prop, ok := p.subProperty[name]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return prop.value
|
||||
}
|
||||
|
||||
type parsedTag struct {
|
||||
properties map[string]property
|
||||
}
|
||||
|
||||
func (p *parsedTag) hasProperty(name string) bool {
|
||||
_, ok := p.properties[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (p *parsedTag) getProperty(name string) (property, bool) {
|
||||
prop, ok := p.properties[name]
|
||||
return prop, ok
|
||||
}
|
||||
|
||||
// one_to_one:"target_entity:Address, join_column:address_id, reference:id"
|
||||
func parseTag(tag reflect.StructTag) *parsedTag {
|
||||
result := &parsedTag{
|
||||
properties: make(map[string]property),
|
||||
}
|
||||
|
||||
tagVal, ok := tag.Lookup("d3")
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
|
||||
for name, value := range extractKVFromTag(tagVal) {
|
||||
d3Property := property{
|
||||
name: name,
|
||||
value: value,
|
||||
subProperty: make(map[string]property),
|
||||
}
|
||||
|
||||
for subName, subValue := range extractKVFromValue(value) {
|
||||
d3Property.subProperty[subName] = property{
|
||||
name: subName,
|
||||
value: subValue,
|
||||
subProperty: make(map[string]property),
|
||||
}
|
||||
}
|
||||
result.properties[name] = d3Property
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func extractKVFromTag(tag string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
|
||||
var i int
|
||||
for tag != "" {
|
||||
for i < len(tag) && tag[i] != ':' {
|
||||
i++
|
||||
}
|
||||
if i >= len(tag) {
|
||||
break
|
||||
}
|
||||
|
||||
name := strings.Trim(tag[:i], " :,")
|
||||
tag = tag[i+1:]
|
||||
i = 0
|
||||
|
||||
for i < len(tag) && tag[i] != '<' {
|
||||
i++
|
||||
}
|
||||
if i >= len(tag) {
|
||||
result[name] = strings.Trim(tag[:i], "\" ,")
|
||||
break
|
||||
}
|
||||
|
||||
tag = tag[i+1:]
|
||||
i = 0
|
||||
for i < len(tag) && tag[i] != '>' {
|
||||
i++
|
||||
}
|
||||
|
||||
result[name] = strings.Trim(tag[:i], "\" ,")
|
||||
if i >= len(tag) {
|
||||
break
|
||||
}
|
||||
tag = tag[i+1:]
|
||||
i = 0
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func extractKVFromValue(tag string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
|
||||
var i int
|
||||
for tag != "" {
|
||||
for i < len(tag) && tag[i] != ':' {
|
||||
i++
|
||||
}
|
||||
if i >= len(tag) {
|
||||
break
|
||||
}
|
||||
|
||||
name := strings.Trim(tag[:i], " :")
|
||||
tag = tag[i+1:]
|
||||
i = 0
|
||||
|
||||
for i < len(tag) && tag[i] != ',' {
|
||||
i++
|
||||
}
|
||||
result[name] = strings.Trim(tag[:i], " ")
|
||||
|
||||
if i >= len(tag) {
|
||||
break
|
||||
}
|
||||
tag = tag[i+1:]
|
||||
i = 0
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
127
orm/entity/tag_test.go
Normal file
127
orm/entity/tag_test.go
Normal file
@ -0,0 +1,127 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTagParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
expect *parsedTag
|
||||
}{
|
||||
{
|
||||
"One Tag",
|
||||
`d3:"one_to_one:<target_entity:Address, join_column:address_id, reference:id>"`,
|
||||
&parsedTag{
|
||||
properties: map[string]property{
|
||||
"one_to_one": {
|
||||
name: "one_to_one",
|
||||
value: "target_entity:Address, join_column:address_id, reference:id",
|
||||
subProperty: map[string]property{
|
||||
"target_entity": {
|
||||
name: "target_entity",
|
||||
value: "Address",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
"join_column": {
|
||||
name: "join_column",
|
||||
value: "address_id",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
"reference": {
|
||||
name: "reference",
|
||||
value: "id",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Two Tags With Unknown",
|
||||
`d3:"one_to_one: <target_entity:Address , join_column: address_id , reference: id> ",unknown_tag:"key1:val1,key2:val2"`,
|
||||
&parsedTag{
|
||||
properties: map[string]property{
|
||||
"one_to_one": {
|
||||
name: "one_to_one",
|
||||
value: "target_entity:Address , join_column: address_id , reference: id",
|
||||
subProperty: map[string]property{
|
||||
"target_entity": {
|
||||
name: "target_entity",
|
||||
value: "Address",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
"join_column": {
|
||||
name: "join_column",
|
||||
value: "address_id",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
"reference": {
|
||||
name: "reference",
|
||||
value: "id",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"Tree Tags",
|
||||
`d3:"one_to_one:<target_entity:Address,join_column:address_id,reference:id>,many_to_one:<target_entity:User>,type:lazy"`,
|
||||
&parsedTag{
|
||||
properties: map[string]property{
|
||||
"one_to_one": {
|
||||
name: "one_to_one",
|
||||
value: "target_entity:Address,join_column:address_id,reference:id",
|
||||
subProperty: map[string]property{
|
||||
"target_entity": {
|
||||
name: "target_entity",
|
||||
value: "Address",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
"join_column": {
|
||||
name: "join_column",
|
||||
value: "address_id",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
"reference": {
|
||||
name: "reference",
|
||||
value: "id",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"many_to_one": {
|
||||
name: "many_to_one",
|
||||
value: "target_entity:User",
|
||||
subProperty: map[string]property{
|
||||
"target_entity": {
|
||||
name: "target_entity",
|
||||
value: "User",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
},
|
||||
},
|
||||
"type": {
|
||||
name: "type",
|
||||
value: "lazy",
|
||||
subProperty: map[string]property{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
result := parseTag(reflect.StructTag(testCase.arg))
|
||||
require.Equal(t, testCase.expect, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user