-
Notifications
You must be signed in to change notification settings - Fork 123
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
Adding support for arrays #380
base: master
Are you sure you want to change the base?
Conversation
…testcases on macOs
Hi @arjen-ag5, thanks for starting the work on arrays. |
if column.DataType.Dimensions > 1 { | ||
return "" | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multi dimensional arrays are just arrays containing other arrays. In our case Array[Array[StringExpression]]
. But it is fine with PR to go with single dimension arrays only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, but since we don't have a good model type yet, I haven't included this case yet
case "bigint[]": | ||
return pq.Int64Array{} | ||
case "text[]", "jsonb[]", "json[]": | ||
return pq.StringArray{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine using pq
types for one dimensional arrays. Maybe pgtype
can be used for multi dimensional arrays.
Other types as well, time, date, etc...
typeName := columnMetaData.DataType.Name | ||
columnName := columnMetaData.Name | ||
|
||
if columnMetaData.DataType.Kind == metadata.ArrayType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not add a case switch for each of the array types, as it is already done for other types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a new sqlArrayToColumnType
to convert a type to an array type. The function switches on the type name without the []
suffix because you would have to add infinite brackets for multidimensional arrays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I think about it again it makes sense to separate arrays from sqlToColumnType
. Since every postgres type can be element of array([]bool
, []point
, []timestamp
, etc...), sql builder array type can be constructed with just sqlToColumnType
:
columnType = sqlToColumnType(strings.TrimSuffix(typeName, "[]")) + "Array"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can lead to incorrectly generated code, because this PR does not support DateArray for example. What's your opinion on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be supported with one line type DateArray Array[DateExpression]
. For model type we can fallback to string if there is no pq
date array type.
@@ -190,8 +222,7 @@ func getSqlBuilderColumnType(columnMetaData metadata.Column) string { | |||
case "numrange": | |||
return "NumericRange" | |||
default: | |||
fmt.Println("- [SQL Builder] Unsupported sql column '" + columnMetaData.Name + " " + columnMetaData.DataType.Name + "', using StringColumn instead.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if we add array types, there still gonna be some unsupported types, so warning message can remain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The warning is handled in the caller
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, I see. Maybe we an error would be more in go style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about?
// sqlToColumnType maps the type of a SQL column type to a go jet sql builder column. The second return value returns
// whether the given type is supported.
func sqlToColumnType(typeName string) (string, bool) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that is fine as well.
internal/jet/array_expression.go
Outdated
package jet | ||
|
||
// ArrayExpression interface | ||
type ArrayExpression[E Expression] interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just Array[E Expression]
, so it follows naming convention with Range
. Also Expression
would repeat twice in the name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Done
internal/jet/array_expression.go
Outdated
CONCAT(rhs ArrayExpression[E]) ArrayExpression[E] | ||
CONCAT_ELEMENT(E) ArrayExpression[E] | ||
|
||
AT(expression IntegerExpression) Expression |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AT
should return element type E
. Take a look at rangeTypeCaster
how to extract element type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/jet/expression.go
Outdated
@@ -316,6 +316,32 @@ func (s *complexExpression) serialize(statement StatementType, out *SQLBuilder, | |||
} | |||
} | |||
|
|||
type arraySubscriptExpression struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the old way to construct custom expression. The new and easier way is using CustomExpression
and Token
. In this case: CustomExpression(array, Token("["), index, Token("]"))
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/jet/literal_expression.go
Outdated
literalExpressionImpl | ||
} | ||
|
||
func UnsafeArray[E LiteralExpression](values []interface{}) ArrayExpression[E] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was experimenting around, forgot to remove it. Gone now
internal/jet/literal_expression.go
Outdated
literalExpressionImpl | ||
} | ||
|
||
func Int64Array(values []int64) ArrayExpression[IntegerExpression] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be done without new literal types. Some of these files need some cleaning.
func Int64Array(values []int64) ArrayExpression[IntegerExpression] {
return ArrayExp[IntegerExpression](literal(pq.Int64Array(values))
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleanedup
@@ -253,6 +255,19 @@ func argToString(value interface{}) string { | |||
} | |||
} | |||
|
|||
func stringArrayQuote(val []string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can use pq.StringArray{}.Value()
instead, and cast first returned value to string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like this?
func stringArrayQuote(val []string) string {
// We'll rely on the internals of pq2.StringArray here. We know it will never return an error, and the returned
// value is a string
dv, _ := pq2.StringArray(val).Value()
return dv.(string)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, exactly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/jet/string_expression.go
Outdated
@@ -16,6 +16,9 @@ type StringExpression interface { | |||
BETWEEN(min, max StringExpression) BoolExpression | |||
NOT_BETWEEN(min, max StringExpression) BoolExpression | |||
|
|||
ANY_EQ(rhs ArrayExpression[StringExpression]) BoolExpression | |||
ALL_EQ(rhs ArrayExpression[StringExpression]) BoolExpression | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we add ANY_EQ
, we would also need to add, for each of the types, NOT_EQ_ANY, LT_ANY, LT_EQ_ANY, etc... This would lead to number of methods explosion.
Simpler way would be to have ANY
and ALL
as generic functions:
func ANY[E Expression](array Array[E]) E {
// check rangeTypeCaster
}
Then we can write for any type:
Table.SomeStringColumn.EQ(ANY(Table.StringArrayColumn))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that makes sense. I'll change that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
postgres/cast.go
Outdated
@@ -45,6 +45,10 @@ type cast interface { | |||
AS_INTERVAL() IntervalExpression | |||
} | |||
|
|||
type castArray interface { | |||
AS_STRING() jet.ArrayExpression[StringExpression] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why separate interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was left over cruft from experimentation, removed it
tests/postgres/scan_test.go
Outdated
@@ -967,26 +969,25 @@ func TestScanIntoCustomBaseTypes(t *testing.T) { | |||
ReplacementCost MyFloat64 | |||
Rating *model.MpaaRating | |||
LastUpdate MyTime | |||
SpecialFeatures *MyString | |||
SpecialFeatures MyStringArray |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If SpecialFeatures
is pq.StringArray you wouldn't need to exclude SpecialFeatures column bellow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the type alias like so:
type MyStringArray pq.StringArray
The MyStringArray type does not implement Scanner. The QRM will then see it as a slice because that's what pq.StringArray
is and try to apply it's row mapping into the slice. Therefore it will always be null.
Adding a method like this would work:
func (a *MyStringArray) Scan(src interface{}) error {
x := (*pq.StringArray)(a)
return x.Scan(src)
}
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I'm meant without defining a type, either by realiasing or not using new type at all:
type MyStringArray = pq.StringArray // note '=', MyStringArray now retains Scan method
or
type film struct {
FilmID MyUint16 `sql:"primary_key"`
Title MyString
Description *MyString
ReleaseYear *MyInt16
LanguageID MyUint8
RentalDuration MyUint8
RentalRate MyFloat32
Length *MyUint32
ReplacementCost MyFloat64
Rating *model.MpaaRating
LastUpdate MyTime
SpecialFeatures *MyString
SpecialFeatures pq.StringArray
}
@@ -2,6 +2,7 @@ package postgres | |||
|
|||
import ( | |||
"database/sql" | |||
"github.com/lib/pq" | |||
"testing" | |||
"time" | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for testing, maybe we can have a new array only table, similar to what's already been done for range tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that test files are in separate repo: https://github.com/go-jet/jet-test-data
} | ||
|
||
func ARRAY_APPEND[E Expression](arr Array[E], el E) Array[E] { | ||
return arrayTypeCaster[E](arr, Func("array_append", arr, el)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uppercase "array_append".
@@ -11,6 +11,11 @@ func Bool(value bool) BoolExpression { | |||
return CAST(jet.Bool(value)).AS_BOOL() | |||
} | |||
|
|||
// BoolArray creates new bool array literal expression | |||
func BoolArray(elements []bool) BoolArrayExpression { | |||
return jet.BoolArray(elements) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll probably need an explicit cast here as well, since cockroachdb tends to have problem with untyped parameters.
hey, would this be merged any time sooner? Have been using go-jet for a production project (https://wapikit.com) and just noticed, it does not supports arrays yet. |
Hello,
I'm working on support for arrays in go-jet. I was wondering if this is a feature that is considered in the long run and if it maybe merged into upstream in the long term. I'd like to show the progress I made so far and if it's any use trying to get it merged at all. If not I will not spend any effort in making it work with the different database types. Currently I only implemented postgres.
I can see from the Range support that is added earlier that the types from the
pgtypes
packages are being used. I took this into consideration for use as the SQL types in the generated table types. But thepgtypes.Array*
structs are tailored to represent the internal representation of the postgresql array types. In constrast to thepq.Array*
types which are much more user friendly, thats why I chose to use the latter.