Closed Julio-Guerra closed 3 years ago
CC @kardianos
I suspect this will be a "no". Have you explored a custom connector that then exposes this functionality?
It sounds like this is errors.Unwrap for sql.Driver. But why are sql.Drivers being wrapped so much? What is the larger context here?
A reason we use sql driver wrapping at ZipRecruiter is to extract tracing information from a context.Context and inject it into the actual query as a comment to achieve basic tracing. This way if a query is taking a long time to run you can see who initiated the query. https://github.com/ngrok/sqlmw is a package that assists with this and could trivially implement the proposal here.
OK, so I understand the point of having an "add tracing of SQL commands" wrapper driver. But then the next question is: why would you need to unwrap an arbitrary wrapped driver?
In general unwrapping is not a safe operation. Supposed you had, as a dumb example, a rot13-wrapping driver that stored all the data rot13'd (and did the reverse on the way back out). If you unwrapped to the raw driver underneath, any use of the calls would break the rot13 invariant. This comes up all the time for various things the wrappers are trying to ensure. We struggled a lot with errors here and for that specific case it was important to get at the underlying ones to test for specific errors and so on. But in general - for example in the FS API that we just adopted - unwrapping is not something that is safe and should be done.
So why is unwrapping important for SQL drivers? When is it necessary, and why?
A common pattern (across languages) is to use the driver to figure out what sql variant to use. For example pagination is different in almost all relational databases. I don't see a clear way (other than running queries against the database) to interrogate the DB object to figure out what kind of database you might be connected to. That's what's mentioned in the original post (dialect inference.)
Frankly, I would suggest that sql generators (ORMs) not infer based on connection but instead outer layers, but I bet that's the main thought here.
I wrote some MySQL wrappers at my last job and many ORMs allow to pass in a string or other identifier to tell it the "flavor" of the connection vs trying to type assert it. This is probably preferred since even a Unwrap method would still require modifications to the ORM/generator.
This sounds like that it should be specified when creating ORM itself rather than derived from the driver or queries. e.g. orm.FromPostgres(db)
.
For example, let's say there's a proxy driver, how would you query the instance to determine what dialect it's using? In principle the exact same driver/connector type could be used for different dialects.
Well, in projects where I've come across this in the past, you start by discovering the driver, and if it's a multi-db driver (ODBC is an example of that) you then do queries against the DB to figure out what it is. Again: I'm not emphasizing that this should be done, but if you do it, this is how.
... discovering the driver, and if it's a multi-db driver ...
How do you know it's a multi-db driver in the first place?
So why is unwrapping important for SQL drivers? When is it necessary, and why?
Adding our application security agent use-case too: our sql-injection detection needs to know what is the SQL dialect being used. To do so, we read the driver package path and maintain a map of the package path's dialects (eg. github.com/mattn/go-sqlite3 is sqllite). That is the current outcome of the discussion at https://github.com/golang/go/issues/12600
But when the driver is wrapped, we get the wrapper package path instead, and we can no longer infer the dialect.
Note that our use-case would be definitely better solved by a specific way of getting the dialect being used at run time.
How do you know it's a multi-db driver in the first place?
In the same way that you have code mapping drivers to dialects, you end up with code that can do more and more accurate detection. The same thing needs to be done even within a single driver if you care about different support as a single engine evolves.
I'm not sure describing this in more depth is helpful for this discussion any more. If someone is interested let me know and I can point at some resources.
On Wed, Dec 9, 2020, 11:41 PM Egon Elbre notifications@github.com wrote:
... discovering the driver, and if it's a multi-db driver ...
How do you know it's a multi-db driver in the first place?
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/golang/go/issues/42460#issuecomment-742319896, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAB7Y6RJQW5BTRSDR7WT3DSUB3RZANCNFSM4TPO5SIQ .
I'm not hearing a compelling use case for Unwrap. These seem like less-than-elegant uses that would not be 100% satisfied by Unwrap. Egon's comment above about orm.FromPostgres seems like a cleaner solution. (And if you want a 90% solution, instead of Unwrap you could use reflect to poke around.)
Based on the discussion above, this proposal seems like a likely decline. — rsc for the proposal review group
This proposal is motivated by the common need for a standard interface defining a standard way to unwrap SQL drivers that may have been wrapped by SQL instrumentation packages.
Problem
Such SQL driver wrapper types hide every other interface the wrapped type may implement. For example,
database/sql/driver
defines many optional interfaces that would be hidden by the following wrapper type definition:With this straightforward type definition,
myDriverWrapper
only implements interfacedriver.Driver
, no matter what the actual driver may implement besides thedriver.Driver
interface.SQL dialect detection functions based on the package path name gotten with
reflect
(withreflect.TypeOf(db.Driver()).PkgPath()
) will also break when using the wrapped driver.Proposed solution
Similarly to the
Unwrap
function of packageerrors
(https://golang.org/pkg/errors/#Unwrap), packagedatabase/sql/driver
could provide theUnwrapper
interface allowing a driver wrapper to return its underlying SQL driver, but also let third-party packages know about it:When a driver wrapper implements the
Unwrapper
interface, a third-party package is able to: