添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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 status: in discussion For issues and PRs. Community and maintainers are discussing the applicability of the issue. type: feature For issues and PRs. For new features. Never breaking changes. fogine, emckay, Chris911, markcarey, jessethomson, KoteKotlyarov, alexcrack, maxzhou0, bradisbell, seromenho, and 34 more reacted with thumbs up emoji irvanherz, reduardo7, EinfachHans, kislaykantidhar, and kylefarris reacted with rocket emoji All reactions

This is not built in, and since this is not a feature rquest we've see before, it's probably low on the list

Your best bet would probably be to hook directly into the query generator. But mind you, that it's not part of the public API and the arguments might be slightly different.

May I ask why you want the SQL without executing the query?

Sql listeners have beeen removed after we went from event emitters to promises

I have been thinking a bit about the way to do this, and I have an idea - not sure how good / pretty it is though ...

Basically you want to be able to do all the stuff that you normally want with sequelize, except sending the query to the DB. So ideally:

var sql = Model.findAll({ 
  where { foo { $gt: 'bar' }}
});

However, findAll contains promise chains that we cannot easily circumvent. This means that this is more likely

Model.findAll({ 
  where { foo { $gt: 'bar' }}
}).then(function (sql) {
});

Its not pretty, but its the way that would require the least amount of changes.

I'm imagining you either do Model.sql().findAll or pass { sql: true } as an option to the call - which would make sequelize.query return the sql instead of executing it.

Another option might be to limit the sql generation to select / at least to a few query types. In that case we could create a sync function

Model.prototype.select = function (options) { return queryinterface.selectQuery(options)

+1 for this feature and +1 for { sql: true } option.

My use case is to get the sql schema of a model (a.k.a CREATE TABLE sql), so that I can use a sql diff tool for auto-migration. With { sql: true } option, I can use: User.force({ sql: true }).then(executeSQLDiff) .

btw, is there any other existing way to get the sql schema of a model?

{ sql: true } will become an option that entirely changes the behavior of the function. Model.sql() clearly expresses the intention and also it will make it easier to maintain since no function will have to be patched to understand that new option. I think Model.sql() is easier to implement, maintain and express. Besides { sql: true } in the options can become a very rare breaking change if some existing code pass evil multi-purpose options object around that happen to have a sql option!

P.S. all functions defined on the object returned by Model.sql() can be synchronous, since no round-tripping is necessary. @luin isn't

let oldQuery = getTheOldOne(); 
let newQuery = User.sql().force();
let diffReport = diff(oldQuery, newQuery);

more expressive than

User.force({ sql: true }).then(newQuery => {
    let oldQuery = getTheOldOne();
    let diffReport = diff(oldQuery, newQuery);
});

@subjectix even if the sql doesn't hit the database, the call will likely still be async due to hooks.

If you look at the code for findAll you'll see that calling hooks is wrapped in a promise. That means that adding .sql that returns the sql synchronously would take a lot more effort than just making .query return the sql.

With something like co it would be a lot easier since yield runHooks would not need to be wrapped in a promise - it can simply return a promise or a sync value depending on the mode. This would then mean that async hooks could not run in sql mode, so perhaps it would be better to not hooks at all in sql mode?

I completely agree that having it synchronous would be ideal, but it would require a fair deal of work to achieve

I'd suggest against adding more dependencies to the project that it needs. If it must be async then let it be. Doesn't really matter. But if the functions can be somehow re-written to be more defensive in the sense of being sync and also ignore hooks and other async things then I guess they won't need co . But can that actually happen? I don't really know if dialects would like that. Is implementing this feature feasible?

Ah yeah tin that case, it doesn't make any sense to add co . Users will just have to do let sql = yield User.findAll if the want something that looks synchronous. Making two 'different versions' (code paths) of each method depending on whether we are generating sql or not would be too error prone and too hard to maintain

Making two 'different versions' (code paths) of each method depending on whether we are generating sql or not would be too error prone and too hard to maintain

You do have a point there :D

I've implemented a function that will get the SQL for a findAll query. I deleted my previous comment because my newer implementation is better and doesn't rely on exception handling. Essentially, it returns a Promise within the beforeFindAfterOptions hook that will never be resolved, so it will never hit the database.
This function returns a Promise that resolves to a SQL string, minus the semicolon at the end.

function getSqlFromFindAll(Model, options) {
    const id = uuid.create();
    return new Promise((resolve, reject) => {
        Model.addHook('beforeFindAfterOptions', id, options => {
            Model.removeHook('beforeFindAfterOptions', id);
            resolve(Model.sequelize.dialect.QueryGenerator.selectQuery(Model.getTableName(), options, Model).slice(0, -1));
            return new Promise(() => {});
        return Model.findAll(options).catch(reject);

Doesn't work fine when there are parallel findAll at the same table, returning a Promise that never solves sounds to me like a memory leak.

I've managed to avoid Promise hanging as well as thrown exception by copying options when providing it { ...options }

@jy95 maybe for select queries yes, but as I understand this feature request is for getting any sql query that would be generated by sequelize, also including but not limited to inserts, updates and deletes.

But that might be a good start, yes...

Just wanted to chime in that I would love this feature as well, like others my use case is that I have a more complex query but would like to leverage some of my existing sequelize-object-query components to populate parts of the query instead of having duplicated where clause construction.

(e.g. we have a generateQueryParams method which returns a sequelize query params object, and I would like to use that to drive the complex query.)

Started a PR here: https://github.com/sequelize/sequelize/pull/11881/files
Please let me know if you like it or what can be improved or what am I missing that might have issues.

Thanks

This is actually a really important feature as sequelize can't build certain queries. For instance, if you are querying a model with 1:n association and then want to order by another nested association. After jumping through all the hoops of options and awkward order: [{ model: ModelName, as: 'associationName' }, { model: ModelTwo: as 'deep'} ], 'field', ASC] and subQuery option... you just can't get it.

What happens is you limit the primary model you're running the find on. Then you order the joined records ... On top of the limited set. So you can't sort by that nested association. Unless you make required: true then it works ...but... you then end up with another problem if that association is optional (plus a findAndCountAll is wrong because distinct doesn't work - though I think I saw a patch for that in beta v6, even still I use a separate count() anyway, it doesn't solve the first problem).

Maybe there's a creative way of making a virtual record with custom options with the include?

At any rate, before I pull another tool in here to help make queries...if you could pull the generated query from the functions, find, findAll, count, etc. without running them, we'd be in real good shape. Because you can then make subQuery: false and apply the limit and order to the outside of everything.

With ORMs, it's basically essential to always provide access into what's going on because it's virtually impossible to cover all bases. Though this kind of query should be common enough. Even if there was some crazy complex thing...I'd leave Sequelize's findAll (and I use scopes to help out too here, which are great) as my own sub query. Then I'd wrap that or join it even.

If I can use the queries Sequelize generates kinda like CTEs or something. As like composable chunks of SQL queries, then awesome. I'd probably always have everything I'd ever need.

Is it even possible already to build subqueries from normal finder options? AND scopes. I've used the generator before with selectQuery() to make sub queries for IN () and such, but couldn't figure out how to get it to work with scopes. So after I went through writing all these scopes, I'd have to basically re-write everything.

Thanks! I do appreciate the package and hard work that went into it all.

Thanks for the link to that! It's pretty easy to just take that and replace the select function where ever you need it. Took me all of 5 minutes to get working. So that's cool. I'm ok with patching it like that. Though making sure the hooks aren't going to run will be next here. Pretty simple, hopefully that gets merged into the various versions (though ideally it would be for more than just select). I still need to upgrade to v5.

edit: well, you can just pass hooks: false in the same options when you pass getRawSql as well. Good enough for now.

Here, you can add a hook to your model even:

const forRawSql = () => {
    YourModel.QueryInterface.select = function (model, tableName, optionsArg) {
      const options = Object.assign({}, optionsArg, { type: this.sequelize.QueryTypes.SELECT, model });
      const query = this.QueryGenerator.selectQuery(tableName, options, model);
      if (options.getRawSql) {
        return Promise.resolve({query});
      return this.sequelize.query(
        query,
        options
    return { getRawSql: true };
  YourModel.addScope('forRawSql', forRawSql);

Still have use hooks: false outside the scope for whatever reason. I can look more into that, but even still not bad unless you use a lot of hooks on selects. I don't really so it won't matter much.

To use the hook above, just like any other:

const q = YourModel.scope(
    method: ['forRawSql'],
  // any others...
const sql = await q.findAll({ hooks: false }); // hooks: false is optional of course
// sql now should be { query: 'SELECT * FROM ...' }

Of course after this, you still need to glue them back together so to speak. It's going to return an array of flattened objects. So you'd need to loop or reduce to get the nested associations. I was trying the option for mapToModel but didn't seem to work. Ah, but the nest: true does. Sorta. You still need to combine records, but each one is at least transformed with dottie.

EDIT: This may lead to issues, and you may prefer to simply override select() at a higher point instead. Either way, the changes in that PR above can be used w/o it being merged in and they are fairly minor edits which is nice.

@DanielWeinerBT set me on the right path with this. However, the approach doesn't work when generating multiple raw queries asynchronously, or when running findAll calls along with generating raw queries.
I've expanded on his code, and yes, it's one giant hack, might not work for future versions of sequelize, but hell, it might help someone in the same situation, so here it is: https://gist.github.com/slavivanov/29b9f479219b4e3a1b559a2468886e2f

Thank you! Will keep an eye on this...

But as per your use case, hashing the inputs would work as well.

Would you recommend just hashing & stringifying the options object?

I think we should consider reworking QueryGenerator to fully make it a public API. Right now, it's too much of an internal API.

You should be able to call QueryGenerator#selectQuery(modelOrTable: ModelOrTable, options: FindOptions), with the exact same parameters you would pass to Model.findAll, and it would return the right SQL.

In order to do that, we'll need to move a lot of logic from Model.findAll & other model methods, to QueryGenerator, but I think it's worth it.

Ideally, Model methods should be limited to a single call to QueryInterface & hooks
QueryInterface should be limited to a call to QueryGenerator and a call to Sequelize#queryRaw.

@DanielWeinerBT set me on the right path with this. However, the approach doesn't work when generating multiple raw queries asynchronously, or when running findAll calls along with generating raw queries. I've expanded on his code, and yes, it's one giant hack, might not work for future versions of sequelize, but hell, it might help someone in the same situation, so here it is: https://gist.github.com/slavivanov/29b9f479219b4e3a1b559a2468886e2f

@slavivanov Is there a way to get count query too? The same approach is not working to get count raw query , I am using beforeCount hook and then calling selectQuery method , but this is giving error

I think we should consider reworking QueryGenerator to fully make it a public API. Right now, it's too much of an internal API.

You should be able to call QueryGenerator#selectQuery(modelOrTable: ModelOrTable, options: FindOptions), with the exact same parameters you would pass to Model.findAll, and it would return the right SQL.

In order to do that, we'll need to move a lot of logic from Model.findAll & other model methods, to QueryGenerator, but I think it's worth it.

Ideally, Model methods should be limited to a single call to QueryInterface & hooks QueryInterface should be limited to a call to QueryGenerator and a call to Sequelize#queryRaw.

Something that this allows is for developers to create custom caching strategies built on top of Sequelize. For example, in Ruby's ActiveRecord implementation, there's a low level model cache which works like this:

Before a query is executed as the developer intended, the model modifies it and sends a minified version of it to the database, which only selects a couple of the attributes, we'll use id and updated_at in this example.

So, developer fires await Product.findAll(), but through an interceptor, the query is modified initially, and instead of it, we fire

select md5(array_agg(id || '-' || updated_at)::text) from projects

This will return an unique hash based on the id and updated_at values that matched the query.

We will use this key as a version and we will try to get the result from the cache, like so cache.get(projects:${version}`).

If there's no hit, we will execute the original query and cache the result.

To generate the hash, though, the query must be aggregated, which is currently problematic, because from the interceptor you would need to modify the parameters passed by the developers and it gets very clunky with custom ordering/grouping.

A much simpler scenario would be to create a view or a subquery from the original sql, and wrap it with the query which generates the hash, so that it runs directly on the results of the original query.

This doesn't work, though, because there's no way to get the sql before it is executed.

This is probably a deal breaker for the majority of cache implementations. You could try to stringify the options but even that gets tricky, because symbols are not serialized properly, so that must be accounted for.

And if you want to execute arbitrary sql on top of the original query, you're pretty much out of luck.

I think having the ability to access the raw sql would make sequelize much more viable to be used at scale

We really need this or expanded abilities of sequelize so that it can create more query types(probably harder?)

Right now there exists a trap of sorts - when you write a fairly large function, generating a query using sequelize with many options, test it, deploy it, and then later find out you want to use one option Sequelize doesn't support... Then you have the options of rewriting all of it to generate raw SQL (not trivial) or extract and modify the sql from sequelize.

status: in discussion For issues and PRs. Community and maintainers are discussing the applicability of the issue. type: feature For issues and PRs. For new features. Never breaking changes.