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

SQL scanner/valuer #44

Closed
HT808s opened this issue Mar 15, 2019 · 8 comments
Closed

SQL scanner/valuer #44

HT808s opened this issue Mar 15, 2019 · 8 comments

Comments

@HT808s
Copy link

HT808s commented Mar 15, 2019

I'd like to do the implementations of scanner and valuer for Money:

Consider this:

Money {
  Amount: 10,
  Currency: EUR,
}

I see several ways to represent it in database.

Solution A

// Scanner and Valuer implemented on `money.Money`.
type someModel struct {
  Money money.Money,     `db:"money"`
}
INSERT INTO foo (money) VALUE ('EUR 10');

A string field in database holding both the amount and the currency.

  • Not suitable for search in SQL environment
  • Conveniently aggregates the currency and the amount in the model.

Solution B

Represent two distinct fields in database, one for the amount and the other for the currency.

  • Efficient select queries can be performed on amount and/or currency.
  • Compels you to use in the model money.Amount and money.Currency.
// Scanner and Valuer implemented on `money.Amount` and `money.Currency`.
type someModel struct {
  Amount money.Amount,     `db:"amount"`
  Currency money.Currency  `db:"currency"`
}
INSERT INTO foo (amount, currency) VALUE (10, 'EUR');

I prefer solution B, what do you think @Rhymond ?

@WLBF
Copy link

WLBF commented Mar 28, 2019

Solution B +1

@djui
Copy link

djui commented May 1, 2019

I’m also in favor of option B, it has more flexibility but will require a bit more work from the user. One can always write their own scanner/valuer on a wrapper type, but the other way around is more work.

@voodoo-dn
Copy link

Solution B +1

@Rhymond
Copy link
Owner

Rhymond commented Jan 5, 2020

I really like solution B, could you please create PR for this?

@jonas-jonas
Copy link

Is there an update on this? Could really use this functionality.

davidalpert added a commit to davidalpert/go-money that referenced this issue Nov 29, 2021
davidalpert added a commit to davidalpert/go-money that referenced this issue Nov 29, 2021
Money's Scan() function assumes that it receives
a single column where the src value is a comma-
separated string in the format "amount,currency_code"

This can be done in SQLite like this:

    SELECT amount || "," || currency as 'amount'

Because the driver.Valuer interface only returns
a single value, it does not make sense to implement
driver.Valuer on Money and it is left to the client
to save the Amount and Currency into two separate
fields.
davidalpert added a commit to davidalpert/go-money that referenced this issue Nov 29, 2021
davidalpert added a commit to davidalpert/go-money that referenced this issue Nov 29, 2021
strings.Split(src,,) will return
a slice with length 2 even if
one of the strings is empty
@davidalpert
Copy link
Contributor

@Rhymond I have created a PR for this issue (#99).

I first implemented the driver.Valuer and sql.Scanner interface on the money.Amount and money.Currency types but ran into trouble when trying to use them implicitly through a money.Money object.

This led me to some research where I learned that the recommended way to implement sql.Scanner when scanning requires multiple fields is to join the parts together into some pattern (e.g. a comma-separated string) which the scanner can rely on and parse.

I have tested #99 in my own project using golang's replace reature in my go.mod file:

replace github.com/Rhymond/go-money => github.com/davidalpert/go-money v1.0.4-0.20211129052335-28ea3cdd185c

and successfully deserialized two separate columns into a single money.Money value.

davidalpert added a commit to davidalpert/go-money that referenced this issue Aug 8, 2022
This allows any Golang ORM which supports the sql.Scanner to
serialize (via sql.Driver) and deserialize (via sql.Scanner) a
money.Currency instance.

money.Amount is now a type alias to int64 which is already supported
by sql.Scanner as one of the core built-in data types
davidalpert added a commit to davidalpert/go-money that referenced this issue Aug 8, 2022
Money's Value() function enables compatible
sql drivers to serialize a money.Money instance
to a single comma-delimited string value of
"amount,currency_code"

Money's Scan() function assumes that it receives
a single column where the src value is a comma-
delimited string in the format
"amount,currency_code"

While the storage format is up to the client when
the amount and currency are stored separately
a compatible scanner value can be created in
SQLite like this:

    SELECT amount || "," || currency as 'amount'

It is left to the client to decide to use Money's
Valuer implementation with a db annotation on
a property of type Money or else to store the
Amount and Currency values as two separate
columns and return them as a single joined
string field.
davidalpert added a commit to davidalpert/go-money that referenced this issue Aug 8, 2022
strings.Split(src,,) will return
a slice with length 2 even if
one of the strings is empty
davidalpert added a commit to davidalpert/go-money that referenced this issue Jan 21, 2024
This allows any Golang ORM which supports the sql.Scanner to
serialize (via sql.Driver) and deserialize (via sql.Scanner) a
money.Currency instance.

money.Amount is now a type alias to int64 which is already supported
by sql.Scanner as one of the core built-in data types
davidalpert added a commit to davidalpert/go-money that referenced this issue Jan 21, 2024
Money's Value() function enables compatible
sql drivers to serialize a money.Money instance
to a single comma-delimited string value of
"amount,currency_code"

Money's Scan() function assumes that it receives
a single column where the src value is a comma-
delimited string in the format
"amount,currency_code"

While the storage format is up to the client when
the amount and currency are stored separately
a compatible scanner value can be created in
SQLite like this:

    SELECT amount || "," || currency as 'amount'

It is left to the client to decide to use Money's
Valuer implementation with a db annotation on
a property of type Money or else to store the
Amount and Currency values as two separate
columns and return them as a single joined
string field.
davidalpert added a commit to davidalpert/go-money that referenced this issue Jan 21, 2024
strings.Split(src,,) will return
a slice with length 2 even if
one of the strings is empty
@FlameMida
Copy link

any update?I'm looking forward it

Rhymond added a commit to davidalpert/go-money that referenced this issue Apr 26, 2024
Rhymond added a commit that referenced this issue Apr 26, 2024
…#99)

* GH-44 implement driver.Valuer and sql.Scanner for money.Currency

This allows any Golang ORM which supports the sql.Scanner to
serialize (via sql.Driver) and deserialize (via sql.Scanner) a
money.Currency instance.

money.Amount is now a type alias to int64 which is already supported
by sql.Scanner as one of the core built-in data types

* GH-44 implement driver.Value and sql.Scanner for money.Money

Money's Value() function enables compatible
sql drivers to serialize a money.Money instance
to a single comma-delimited string value of
"amount,currency_code"

Money's Scan() function assumes that it receives
a single column where the src value is a comma-
delimited string in the format
"amount,currency_code"

While the storage format is up to the client when
the amount and currency are stored separately
a compatible scanner value can be created in
SQLite like this:

    SELECT amount || "," || currency as 'amount'

It is left to the client to decide to use Money's
Valuer implementation with a db annotation on
a property of type Money or else to store the
Amount and Currency values as two separate
columns and return them as a single joined
string field.

* GH-44 fix an edge case

strings.Split(src,,) will return
a slice with length 2 even if
one of the strings is empty

* fix: money.value tests

* refactor out the currency separator and make it customizable

clients can set money.DBMoneyValueSeparator to determine which
separator (e.g. "," "|" ";" ":" "AS" etc) to use when creating a single
driver.Value object to represent a money.Money instance as a single
string database field.

this allows the money package to support string values such as

10@USD
20;CAD
30|IRD
40 in GBP

etc

---------

Co-authored-by: Raymond <raima220@gmail.com>
@Rhymond
Copy link
Owner

Rhymond commented Apr 26, 2024

@FlameMida it just got merged and released

@Rhymond Rhymond closed this as completed Apr 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants