Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Partial Feature] Table name as field instead of Pragma #179

Open
gzigurella opened this issue Dec 1, 2022 · 5 comments
Open

[Partial Feature] Table name as field instead of Pragma #179

gzigurella opened this issue Dec 1, 2022 · 5 comments
Labels
enhancement New feature or request

Comments

@gzigurella
Copy link

Feature Request

Is your feature request related to a problem?

Trying to dynamically add tables to my ORM based CMS

Describe the solution you'd like

Add a check on Model fields at norm/postgres.nim(134, 96) to see if the object contains the field with the key table, in which case we use it to create the table name

Describe alternatives you've considered

I've considered manipulating nim AST, forking the project for my own purpose, yet I still think this could be an useful feature allowing to expand the usage of norm for web-development along with frameworks like Jester or Prologue

Teachability, Documentation, Adoption, Migration Strategy

Norm's Users may use this feature to reduce duplicated code, keeping cleaner their work and allowing them to test more easily without having to re-build the software.

Conversion from JSON to SQL Table could be easily achieved as well wrapping Model inside a JsonModel

How can norm benefit from this feature in the future? Norm could become the Nim ORM standard by de facto implementing a core feature to build Search Engines and similar on top of this feature see Elasticsearch Json Processor for example

@moigagoo
Copy link
Owner

moigagoo commented Dec 11, 2022

@gzigurella Hi! So you propose there's a special reserved field name "table" or "tableName" for each Model, similar to how every Model has "id"?

What is the advantage of this versus the pragma?

@moigagoo moigagoo added the enhancement New feature or request label Dec 11, 2022
@moigagoo
Copy link
Owner

I mean if you're generating the code for your models anyway, you can generate the pragma too.

@gzigurella
Copy link
Author

gzigurella commented Dec 11, 2022

@moigagoo your assumption on my proposal is correct.

The main difference is the time of use, pragma is expected to be used at compile-time giving instructions to compiler without cluttering the source code.

I suggest to use a special field to make use of the ORM functionalities at runtime , this way new models can be defined at runtime with input-based fields.

In my example I've created a variant model but with the pragma I am able to define it just once, while I may need different tables unknown at compile time but known at runtime through serialization and deserialization.

This way we can fully have a bidirectional ORM (Database-to-Object and Object-to-Database), the first one static and known at compile-time while the latter could be dynamic and allows a better integration with dynamic languages such as JavaScript and Python.

I think checking for this special field could be definetely easier than messing with Nim AST to introduce dynamic pragmas.

tl;dr:
Benefit comes from the use at runtime versus compile-time, which leads to better support of serialization and deserialization of tables from dynamic languages and weak typed languages.

@moigagoo
Copy link
Owner

@gzigurella I think I'm misunderstanding something. Sorry, I'll have to ask more questions :-)

AFAIK if you're generating types, it must be done at compile time anyway. You can't generate a type at runtime. And if you're generating at compile time, it's templates or macros anyway, which means generating a type definition with pragma is about as easy as without one.

Could you please share some code you have? It definitely seems we're talking about different things.

@gzigurella
Copy link
Author

gzigurella commented Dec 11, 2022

@moigagoo it's okay don't worry, maybe I'm using the wrong terminology since I'm not an english native speaker 😄

I'm not currently on the workstation i use to write in Nim, but I'll try to make a reasonable example in the meanwhile.

I agree with you, it's not possible to generate types at runtime, what is possible is to create a proxy like Java reflection does, therefore we cannot define a static type but we can indeed define a type that manipulates the JSON payload it receives, or another kind of serialized object, and mirrors operations onto the database.

I refer to JSON as example because is the one I linked in the first post of this Issue. As you can see from the JsonModel

Using the pragma is possible to create just one of these proxy objects mirrored on the database, since the pragma get evaluated at compile-time and it cannot be changed after.

Therefore if I want to have n proxy objects as tables defined on the database I have to know them at compile-time, which is not always possible.

That's why I suggested to add the table name as a special field in the Model object. There are some drawbacks like excessive overhead to manipulate the proxy, yet it allows to a functional use of dynamic types that are not provided in Nim lang, since it's statically typed.

The most "dynamic" approach to types that Nim offers are Object Variants as far as I know, implementing my suggestion allows to operate easily in cases like this where the object is not known at compile-time but is known at runtime, either because the system we operate with is not in our control or is made up with dynamic and weak types (like JavaScript for example)

For future reference I paste here the JsonModel code, this is a Variant Model I thought to persist and interact with the database through ORM and a RESTful architecture.

import norm/[model, pragmas]
import std/[json, tables, sequtils]
import uuids
 
template evalJSON(arg_type: string, fieldName: string, table: Table[string, string]) =
    case arg_type:
        of "string":
            table[fieldName] = "string"
        of "integer":
            table[fieldName] = "int"
        of "bool":
            table[fieldName] = "bool"
        of "number":
            table[fieldName] = "float"
 
template eval*(value: string, arg_type: string) {.dirty.} =
    var result = nil
    case arg_type:
        of "string":
            result = value
        of "integer":
            result = parseBiggestInt(value)
        of "bool":
            result = parseBool(value)
        of "number":
            result = parseFloat(value)
 
type
    JsonModel* = ref object of Model
        external_reference_uuid* : string #! External Reference UUID useful to crete URI to point at the Object
        table*: string  #! SQL Table Name
        fieldsType*: string #! Json HashTable
        fieldsValue*: string #! Json HashTable
        keys* : string #! Json Array
 
proc newJsonModel*(PK: string, VIEW: string, TABLE_TYPES: string, TABLE_VALUES: string, FIELD_NAMES: string) : VariantModel =
    return JsonModel(external_reference_uuid: PK, table: VIEW, fieldsType: TABLE_TYPES, fieldsValue: TABLE_VALUES, keys: FIELD_NAMES)
 
proc newJsonModel*(tableName: string) : JsonModel =
    var 
        typeTable = initTable[string, string]()
        valueTable = initTable[string, string]()
    var
        jsonTypes : JsonNode = %typeTable
        jsonValues : JsonNode = %valueTable
    return newJsonModel($genUUID(), tableName, $jsonTypes, $jsonValues, "[]")
 
proc newJsonModel*(JSON : JsonNode) : JsonModel =
    if JSON.kind == JObject:
        let uuid = $genUUID() #* Generate UUID for the new Database Table
        var keysArg : seq[string]
        for k in JSON.keys: #* Store all JSON keys as named_fields for the Database Table
            keysArg.add(k)
        var fieldsType = initTable[string, string]()
        var fieldsValue = initTable[string, string]()
        for i in JSON["cols"]:
            let name = i["fieldname"].getStr(uuid&"_field_"&"MISSING_NAME")
            let typeJS = i["type"].getStr("string")
            evalJSON(typeJS, name, fieldsType)
            fieldsValue[name] = i["value"].getStr()
        let jsonTypeMap = %fieldsType
        let jsonValueMap = %fieldsValue
        return newJsonModel(uuid, JSON["table"].getStr(uuid&"_table"), $jsonTypeMap, $jsonValueMap, $(%keysArg))
    else:
        return nil
 
template `[]`*(self: JsonModel, key : string) =
    #! Use in case of non-nullable values
    let typeMap : JsonNode = parseJson(self.fieldsType)
    let valueMap : JsonNode = parseJson(self.fieldsValue)
    eval(valueMap[key], typeMap[key])
    result {.inject.} = e
 
template `{}`*(self: JsonModel, key : string) =
    #! Use in case of nullable values
    let fields = self.keys
    if fields.filter(proc(e:string) : bool = e == key).len == 1:
        self[key]
    else:
        result {.inject.} = nil
 
template `!`* (self: JsonModel, key: string) : string =
    #! Use for Debug purpose
    let typeMap : JsonNode = parseJson(self.fieldsType)
    echo typeMap[key]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants