/* jshint camelcase: false */
/* jshint expr: true */
var chai      = require('chai')
  , Sequelize = require('../../index')
  , expect    = chai.expect
  , Support   = require(__dirname + '/../support')
  , DataTypes = require(__dirname + "/../../lib/data-types")
  , datetime  = require('chai-datetime')
  , async     = require('async')

chai.use(datetime)
chai.config.includeStack = true

var sortById = function(a, b) {
  return a.id < b.id ? -1 : 1
}

describe(Support.getTestDialectTeaser("Includes with schemas"), function () {
  describe('findAll', function () {
    beforeEach(function () {
      var self = this
      this.fixtureA = function(done) {
        self.sequelize.dropAllSchemas().success(function(){
          self.sequelize.createSchema("account").success(function(){
            var AccUser = self.sequelize.define("AccUser", {}, {schema: "account"})
              , Company = self.sequelize.define("Company", {
                  name: DataTypes.STRING
                }, {schema: "account"})
              , Product = self.sequelize.define("Product", {
                  title: DataTypes.STRING
                }, {schema: "account"})
              , Tag = self.sequelize.define("Tag", {
                  name: DataTypes.STRING
                }, {schema: "account"})
              , Price = self.sequelize.define("Price", {
                  value: DataTypes.FLOAT
                }, {schema: "account"})
              , Customer = self.sequelize.define("Customer", {
                  name: DataTypes.STRING
              }, {schema: "account"})
              , Group = self.sequelize.define("Group", {
                  name: DataTypes.STRING
                }, {schema: "account"})
              , GroupMember = self.sequelize.define("GroupMember", {

                }, {schema: "account"})
              , Rank = self.sequelize.define("Rank", {
                  name: DataTypes.STRING,
                  canInvite: {
                    type: DataTypes.INTEGER,
                    defaultValue: 0
                  },
                  canRemove: {
                    type: DataTypes.INTEGER,
                    defaultValue: 0
                  },
                  canPost: {
                    type: DataTypes.INTEGER,
                    defaultValue: 0
                  }
                }, {schema: "account"})

            self.models = {
              AccUser: AccUser,
              Company: Company,
              Product: Product,
              Tag: Tag,
              Price: Price,
              Customer: Customer,
              Group: Group,
              GroupMember: GroupMember,
              Rank: Rank
            }

            AccUser.hasMany(Product)
            Product.belongsTo(AccUser)

            Product.hasMany(Tag)
            Tag.hasMany(Product)
            Product.belongsTo(Tag, {as: 'Category'})
            Product.belongsTo(Company)

            Product.hasMany(Price)
            Price.belongsTo(Product)

            AccUser.hasMany(GroupMember, {as: 'Memberships'})
            GroupMember.belongsTo(AccUser)
            GroupMember.belongsTo(Rank)
            GroupMember.belongsTo(Group)
            Group.hasMany(GroupMember, {as: 'Memberships'})

            self.sequelize.sync({force: true}).done(function () {
              var count = 4
                , i = -1

              async.auto({
                groups: function(callback) {
                  Group.bulkCreate([
                    {name: 'Developers'},
                    {name: 'Designers'},
                    {name: 'Managers'}
                  ]).done(function () {
                    Group.findAll().done(callback)
                  })
                },
                companies: function(callback) {
                  Company.bulkCreate([
                    {name: 'Sequelize'},
                    {name: 'Coca Cola'},
                    {name: 'Bonanza'},
                    {name: 'NYSE'},
                    {name: 'Coshopr'}
                  ]).done(function (err) {
                    if (err) return callback(err);
                    Company.findAll().done(callback)
                  })
                },
                ranks: function(callback) {
                  Rank.bulkCreate([
                    {name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1},
                    {name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1},
                    {name: 'Member', canInvite: 1, canRemove: 0, canPost: 0}
                  ]).done(function () {
                    Rank.findAll().done(callback)
                  })
                },
                tags: function(callback) {
                  Tag.bulkCreate([
                    {name: 'A'},
                    {name: 'B'},
                    {name: 'C'},
                    {name: 'D'},
                    {name: 'E'}
                  ]).done(function () {
                    Tag.findAll().done(callback)
                  })
                },
                loop: ['groups', 'ranks', 'tags', 'companies', function (done, results) {
                  var groups = results.groups
                    , ranks = results.ranks
                    , tags = results.tags
                    , companies = results.companies

                  async.whilst(
                    function () { return i < count; },
                    function (callback) {
                      i++
                      async.auto({
                        user: function (callback) {
                          AccUser.create().done(callback)
                        },
                        memberships: ['user', function (callback, results) {
                          var groupMembers = [
                            {AccUserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
                            {AccUserId: results.user.id, GroupId: groups[1].id, RankId: ranks[2].id}
                          ]

                          if (i < 3) {
                            groupMembers.push({AccUserId: results.user.id, GroupId: groups[2].id, RankId: ranks[1].id})
                          }

                          GroupMember.bulkCreate(groupMembers).done(callback)
                        }],
                        products: function (callback) {
                          Product.bulkCreate([
                            {title: 'Chair'},
                            {title: 'Desk'},
                            {title: 'Bed'},
                            {title: 'Pen'},
                            {title: 'Monitor'}
                          ]).done(function (err) {
                            if (err) return callback(err);
                            Product.findAll().done(callback)
                          })
                        },
                        userProducts: ['user', 'products', function (callback, results) {
                          results.user.setProducts([
                            results.products[(i * 5)+0],
                            results.products[(i * 5)+1],
                            results.products[(i * 5)+3]
                          ]).done(callback)
                        }],
                        productTags: ['products', function (callback, results) {
                          var chainer = new Sequelize.Utils.QueryChainer()

                          chainer.add(results.products[(i * 5) + 0].setTags([
                            tags[0],
                            tags[2]
                          ]))
                          chainer.add(results.products[(i * 5) + 1].setTags([
                            tags[1]
                          ]))
                          chainer.add(results.products[(i * 5) + 0].setCategory(tags[1]))

                          chainer.add(results.products[(i * 5) + 2].setTags([
                            tags[0]
                          ]))

                          chainer.add(results.products[(i * 5) + 3].setTags([
                            tags[0]
                          ]))

                          chainer.run().done(callback)
                        }],
                        companies: ['products', function (callback, results) {
                          var chainer = new Sequelize.Utils.QueryChainer()

                          results.products[(i * 5)+0].setCompany(companies[4])
                          results.products[(i * 5)+1].setCompany(companies[3])
                          results.products[(i * 5)+2].setCompany(companies[2])
                          results.products[(i * 5)+3].setCompany(companies[1])
                          results.products[(i * 5)+4].setCompany(companies[0])

                          chainer.run().done(callback)
                        }],
                        prices: ['products', function (callback, results) {
                          Price.bulkCreate([
                            {ProductId: results.products[(i * 5) + 0].id, value: 5},
                            {ProductId: results.products[(i * 5) + 0].id, value: 10},
                            {ProductId: results.products[(i * 5) + 1].id, value: 5},
                            {ProductId: results.products[(i * 5) + 1].id, value: 10},
                            {ProductId: results.products[(i * 5) + 1].id, value: 15},
                            {ProductId: results.products[(i * 5) + 1].id, value: 20},
                            {ProductId: results.products[(i * 5) + 2].id, value: 20},
                            {ProductId: results.products[(i * 5) + 3].id, value: 20}
                          ]).done(callback)
                        }]
                      }, callback)
                    },
                    function (err) {
                      expect(err).not.to.be.ok
                      done()
                    }
                  )
                }]
              }, done.bind(this))
            }).error(done)
          })
        })
      }
    })

    it('should support an include with multiple different association types', function (done) {
      var self = this

      self.sequelize.dropAllSchemas().success(function(){
        self.sequelize.createSchema("account").success(function(){
          var AccUser = self.sequelize.define("AccUser", {}, {schema: "account"})
            , Product = self.sequelize.define("Product", {
                title: DataTypes.STRING
              }, {schema: "account"})
            , Tag = self.sequelize.define("Tag", {
                name: DataTypes.STRING
              }, {schema: "account"})
            , Price = self.sequelize.define("Price", {
                value: DataTypes.FLOAT
              }, {schema: "account"})
            , Customer = self.sequelize.define("Customer", {
                name: DataTypes.STRING
            }, {schema: "account"})
            , Group = self.sequelize.define("Group", {
                name: DataTypes.STRING
              }, {schema: "account"})
            , GroupMember = self.sequelize.define("GroupMember", {

              }, {schema: "account"})
            , Rank = self.sequelize.define("Rank", {
                name: DataTypes.STRING,
                canInvite: {
                  type: DataTypes.INTEGER,
                  defaultValue: 0
                },
                canRemove: {
                  type: DataTypes.INTEGER,
                  defaultValue: 0
                }
              }, {schema: "account"})

          AccUser.hasMany(Product)
          Product.belongsTo(AccUser)

          Product.hasMany(Tag)
          Tag.hasMany(Product)
          Product.belongsTo(Tag, {as: 'Category'})

          Product.hasMany(Price)
          Price.belongsTo(Product)

          AccUser.hasMany(GroupMember, {as: 'Memberships'})
          GroupMember.belongsTo(AccUser)
          GroupMember.belongsTo(Rank)
          GroupMember.belongsTo(Group)
          Group.hasMany(GroupMember, {as: 'Memberships'})

          self.sequelize.sync({force: true}).done(function () {
            var count = 4
              , i = -1

            async.auto({
              groups: function(callback) {
                Group.bulkCreate([
                  {name: 'Developers'},
                  {name: 'Designers'}
                ]).done(function () {
                  Group.findAll().done(callback)
                })
              },
              ranks: function(callback) {
                Rank.bulkCreate([
                  {name: 'Admin', canInvite: 1, canRemove: 1},
                  {name: 'Member', canInvite: 1, canRemove: 0}
                ]).done(function () {
                  Rank.findAll().done(callback)
                })
              },
              tags: function(callback) {
                Tag.bulkCreate([
                  {name: 'A'},
                  {name: 'B'},
                  {name: 'C'}
                ]).done(function () {
                  Tag.findAll().done(callback)
                })
              },
              loop: ['groups', 'ranks', 'tags', function (done, results) {
                var groups = results.groups
                  , ranks = results.ranks
                  , tags = results.tags

                async.whilst(
                  function () { return i < count; },
                  function (callback) {
                    i++

                    async.auto({
                      user: function (callback) {
                        AccUser.create().done(callback)
                      },
                      memberships: ['user', function (callback, results) {
                        GroupMember.bulkCreate([
                          {AccUserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
                          {AccUserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id}
                        ]).done(callback)
                      }],
                      products: function (callback) {
                        Product.bulkCreate([
                          {title: 'Chair'},
                          {title: 'Desk'}
                        ]).done(function () {
                          Product.findAll().done(callback)
                        })
                      },
                      userProducts: ['user', 'products', function (callback, results) {
                        results.user.setProducts([
                          results.products[(i * 2)+0],
                          results.products[(i * 2)+1]
                        ]).done(callback)
                      }],
                      productTags: ['products', function (callback, results) {
                        var chainer = new Sequelize.Utils.QueryChainer()

                        chainer.add(results.products[(i * 2) + 0].setTags([
                          tags[0],
                          tags[2]
                        ]))
                        chainer.add(results.products[(i * 2) + 1].setTags([
                          tags[1]
                        ]))
                        chainer.add(results.products[(i * 2) + 0].setCategory(tags[1]))

                        chainer.run().done(callback)
                      }],
                      prices: ['products', function (callback, results) {
                        Price.bulkCreate([
                          {ProductId: results.products[(i * 2) + 0].id, value: 5},
                          {ProductId: results.products[(i * 2) + 0].id, value: 10},
                          {ProductId: results.products[(i * 2) + 1].id, value: 5},
                          {ProductId: results.products[(i * 2) + 1].id, value: 10},
                          {ProductId: results.products[(i * 2) + 1].id, value: 15},
                          {ProductId: results.products[(i * 2) + 1].id, value: 20}
                        ]).done(callback)
                      }]
                    }, callback)
                  },
                  function (err) {
                    expect(err).not.to.be.ok

                    AccUser.findAll({
                      include: [
                        {model: GroupMember, as: 'Memberships', include: [
                          Group,
                          Rank
                        ]},
                        {model: Product, include: [
                          Tag,
                          {model: Tag, as: 'Category'},
                          Price
                        ]}
                      ],
                      order: [
                        [AccUser.rawAttributes.id, 'ASC']
                      ]
                    }).done(function (err, users) {
                      expect(err).not.to.be.ok
                      users.forEach(function (user) {
                        expect(user.Memberships).to.be.ok
                        user.Memberships.sort(sortById)

                        expect(user.Memberships.length).to.equal(2)
                        expect(user.Memberships[0].Group.name).to.equal('Developers')
                        expect(user.Memberships[0].Rank.canRemove).to.equal(1)
                        expect(user.Memberships[1].Group.name).to.equal('Designers')
                        expect(user.Memberships[1].Rank.canRemove).to.equal(0)

                        user.Products.sort(sortById)
                        expect(user.Products.length).to.equal(2)
                        expect(user.Products[0].Tags.length).to.equal(2)
                        expect(user.Products[1].Tags.length).to.equal(1)
                        expect(user.Products[0].Category).to.be.ok
                        expect(user.Products[1].Category).not.to.be.ok

                        expect(user.Products[0].Prices.length).to.equal(2)
                        expect(user.Products[1].Prices.length).to.equal(4)

                        done()
                      })
                    })
                  }
                )
              }]
            }, done)
          })
        })
      })
    })

    it('should support many levels of belongsTo', function (done) {
      var A = this.sequelize.define('a', {}, {schema: "account"})
        , B = this.sequelize.define('b', {}, {schema: "account"})
        , C = this.sequelize.define('c', {}, {schema: "account"})
        , D = this.sequelize.define('d', {}, {schema: "account"})
        , E = this.sequelize.define('e', {}, {schema: "account"})
        , F = this.sequelize.define('f', {}, {schema: "account"})
        , G = this.sequelize.define('g', {}, {schema: "account"})
        , H = this.sequelize.define('h', {}, {schema: "account"})

      A.belongsTo(B)
      B.belongsTo(C)
      C.belongsTo(D)
      D.belongsTo(E)
      E.belongsTo(F)
      F.belongsTo(G)
      G.belongsTo(H)

      var b, singles = [
        B,
        C,
        D,
        E,
        F,
        G,
        H
      ]

      this.sequelize.sync().done(function () {
        async.auto({
          as: function (callback) {
            A.bulkCreate([
              {},
              {},
              {},
              {},
              {},
              {},
              {},
              {}
            ]).done(function () {
              A.findAll().done(callback)
            })
          },
          singleChain: function (callback) {
            var previousInstance

            async.eachSeries(singles, function (model, callback) {
              model.create({}).done(function (err, instance) {
                if (previousInstance) {
                  previousInstance["set"+Sequelize.Utils.uppercaseFirst(model.name)](instance).done(function () {
                    previousInstance = instance
                    callback()
                  })
                } else {
                  previousInstance = b = instance
                  callback()
                }
              })
            }, callback)
          },
          abs: ['as', 'singleChain', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()

            results.as.forEach(function (a) {
              chainer.add(a.setB(b))
            })

            chainer.run().done(callback)
          }]
        }, function () {

          A.findAll({
            include: [
              {model: B, include: [
                {model: C, include: [
                  {model: D, include: [
                    {model: E, include: [
                      {model: F, include: [
                        {model: G, include: [
                          {model: H}
                        ]}
                      ]}
                    ]}
                  ]}
                ]}
              ]}
            ]
          }).done(function (err, as) {
            expect(err).not.to.be.ok
            expect(as.length).to.be.ok

            as.forEach(function (a) {
              expect(a.b.c.d.e.f.g.h).to.be.ok
            })
            done()
          })
        })
      })
    })

    it('should support ordering with only belongsTo includes', function(done) {
      var User = this.sequelize.define('SpecialUser', {}, {schema: "account"})
        , Item = this.sequelize.define('Item', {'test': DataTypes.STRING}, {schema: "account"})
        , Order = this.sequelize.define('Order', {'position': DataTypes.INTEGER}, {schema: "account"})

      User.belongsTo(Item, {'as': 'itemA', foreignKey: 'itemA_id'})
      User.belongsTo(Item, {'as': 'itemB', foreignKey: 'itemB_id'})
      User.belongsTo(Order)

      this.sequelize.sync().done(function() {
        async.auto({
          users: function(callback) {
            User.bulkCreate([{}, {}, {}]).done(function() {
              User.findAll().done(callback)
            })
          },
          items: function(callback) {
            Item.bulkCreate([
              {'test': 'abc'},
              {'test': 'def'},
              {'test': 'ghi'},
              {'test': 'jkl'}
            ]).done(function() {
              Item.findAll({order: ['id']}).done(callback)
            })
          },
          orders: function(callback) {
            Order.bulkCreate([
              {'position': 2},
              {'position': 3},
              {'position': 1}
            ]).done(function() {
              Order.findAll({order: ['id']}).done(callback)
            })
          },
          associate: ['users', 'items', 'orders', function(callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()

            var user1 = results.users[0]
            var user2 = results.users[1]
            var user3 = results.users[2]

            var item1 = results.items[0]
            var item2 = results.items[1]
            var item3 = results.items[2]
            var item4 = results.items[3]

            var order1 = results.orders[0]
            var order2 = results.orders[1]
            var order3 = results.orders[2]

            chainer.add(user1.setItemA(item1))
            chainer.add(user1.setItemB(item2))
            chainer.add(user1.setOrder(order3))

            chainer.add(user2.setItemA(item3))
            chainer.add(user2.setItemB(item4))
            chainer.add(user2.setOrder(order2))

            chainer.add(user3.setItemA(item1))
            chainer.add(user3.setItemB(item4))
            chainer.add(user3.setOrder(order1))

            chainer.run().done(callback)
          }]
        }, function() {
          User.findAll({
            'where': {'itemA.test': 'abc'},
            'include': [
              {'model': Item, 'as': 'itemA'},
              {'model': Item, 'as': 'itemB'},
              Order],
            'order': ['Order.position']
          }).done(function(err, as) {
            expect(err).not.to.be.ok
            expect(as.length).to.eql(2)

            expect(as[0].itemA.test).to.eql('abc')
            expect(as[1].itemA.test).to.eql('abc')

            expect(as[0].Order.position).to.eql(1)
            expect(as[1].Order.position).to.eql(2)

            done()
          })
        })
      })
    })

    it('should include attributes from through models', function (done) {
      var Product = this.sequelize.define('Product', {
            title: DataTypes.STRING
          }, {schema: "account"})
        , Tag = this.sequelize.define('Tag', {
            name: DataTypes.STRING
          }, {schema: "account"})
        , ProductTag = this.sequelize.define('ProductTag', {
            priority: DataTypes.INTEGER
        }, {schema: "account"})

      Product.hasMany(Tag, {through: ProductTag})
      Tag.hasMany(Product, {through: ProductTag})

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          products: function (callback) {
            Product.bulkCreate([
              {title: 'Chair'},
              {title: 'Desk'},
              {title: 'Dress'}
            ]).done(function () {
              Product.findAll().done(callback)
            })
          },
          tags: function(callback) {
            Tag.bulkCreate([
              {name: 'A'},
              {name: 'B'},
              {name: 'C'}
            ]).done(function () {
              Tag.findAll().done(callback)
            })
          },
          productTags: ['products', 'tags', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()

            chainer.add(results.products[0].addTag(results.tags[0], {priority: 1}))
            chainer.add(results.products[0].addTag(results.tags[1], {priority: 2}))

            chainer.add(results.products[1].addTag(results.tags[1], {priority: 1}))

            chainer.add(results.products[2].addTag(results.tags[0], {priority: 3}))
            chainer.add(results.products[2].addTag(results.tags[1], {priority: 1}))
            chainer.add(results.products[2].addTag(results.tags[2], {priority: 2}))

            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          Product.findAll({
            include: [
              {model: Tag}
            ],
            order: [
              ['Product.id', 'ASC'],
              ['Tags.id', 'ASC']
            ]
          }).done(function (err, products) {
            expect(err).not.to.be.ok

            expect(products[0].Tags[0].ProductTag.priority).to.equal(1)
            expect(products[0].Tags[1].ProductTag.priority).to.equal(2)

            expect(products[1].Tags[0].ProductTag.priority).to.equal(1)

            expect(products[2].Tags[0].ProductTag.priority).to.equal(3)
            expect(products[2].Tags[1].ProductTag.priority).to.equal(1)
            expect(products[2].Tags[2].ProductTag.priority).to.equal(2)

            done()
          })
        })
      })
    })

    it('should support a required belongsTo include', function (done) {
      var User = this.sequelize.define('User', {}, {schema: "account"})
        , Group = this.sequelize.define('Group', {}, {schema: "account"})

      User.belongsTo(Group)

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          groups: function (callback) {
            Group.bulkCreate([{}, {}]).done(function () {
              Group.findAll().done(callback)
            })
          },
          users: function (callback) {
            User.bulkCreate([{}, {}, {}]).done(function () {
              User.findAll().done(callback)
            })
          },
          userGroups: ['users', 'groups', function (callback, results) {
            results.users[2].setGroup(results.groups[1]).done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          User.findAll({
            include: [
              {model: Group, required: true}
            ]
          }).done(function (err, users) {
            expect(err).not.to.be.ok
            expect(users.length).to.equal(1)
            expect(users[0].Group).to.be.ok
            done()
          })
        })
      })
    })

    it('should be possible to extend the on clause with a where option on a belongsTo include', function (done) {
      var User = this.sequelize.define('User', {}, {schema: "account"})
        , Group = this.sequelize.define('Group', {
            name: DataTypes.STRING
          }, {schema: "account"})

      User.belongsTo(Group)

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          groups: function (callback) {
            Group.bulkCreate([
              {name: 'A'},
              {name: 'B'}
            ]).done(function () {
              Group.findAll().done(callback)
            })
          },
          users: function (callback) {
            User.bulkCreate([{}, {}]).done(function () {
              User.findAll().done(callback)
            })
          },
          userGroups: ['users', 'groups', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()
            chainer.add(results.users[0].setGroup(results.groups[1]))
            chainer.add(results.users[1].setGroup(results.groups[0]))
            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          User.findAll({
            include: [
              {model: Group, where: {name: 'A'}}
            ]
          }).done(function (err, users) {
            expect(err).not.to.be.ok
            expect(users.length).to.equal(1)
            expect(users[0].Group).to.be.ok
            expect(users[0].Group.name).to.equal('A')
            done()
          })
        })
      })
    })

    it('should be possible to extend the on clause with a where option on a belongsTo include', function (done) {
      var User = this.sequelize.define('User', {}, {schema: "account"})
        , Group = this.sequelize.define('Group', {
            name: DataTypes.STRING
          }, {schema: "account"})

      User.belongsTo(Group)

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          groups: function (callback) {
            Group.bulkCreate([
              {name: 'A'},
              {name: 'B'}
            ]).done(function () {
              Group.findAll().done(callback)
            })
          },
          users: function (callback) {
            User.bulkCreate([{}, {}]).done(function () {
              User.findAll().done(callback)
            })
          },
          userGroups: ['users', 'groups', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()
            chainer.add(results.users[0].setGroup(results.groups[1]))
            chainer.add(results.users[1].setGroup(results.groups[0]))
            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          User.findAll({
            include: [
              {model: Group, required: true}
            ]
          }).done(function (err, users) {
            expect(err).not.to.be.ok
            users.forEach(function (user) {
              expect(user.Group).to.be.ok
            })
            done()
          })
        })
      })
    })

    it('should be possible to define a belongsTo include as required with child hasMany with limit', function (done) {
      var User = this.sequelize.define('User', {}, {schema: "account"}) , Group = this.sequelize.define('Group', {
            name: DataTypes.STRING
          }, {schema: "account"})
        , Category = this.sequelize.define('Category', {
            category: DataTypes.STRING
          }, {schema: "account"})

      User.belongsTo(Group)
      Group.hasMany(Category)

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          groups: function (callback) {
            Group.bulkCreate([
              {name: 'A'},
              {name: 'B'}
            ]).done(function () {
              Group.findAll().done(callback)
            })
          },
          users: function (callback) {
            User.bulkCreate([{}, {}]).done(function () {
              User.findAll().done(callback)
            })
          },
          categories: function (callback) {
            Category.bulkCreate([{}, {}]).done(function () {
              Category.findAll().done(callback)
            })
          },
          userGroups: ['users', 'groups', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()
            chainer.add(results.users[0].setGroup(results.groups[1]))
            chainer.add(results.users[1].setGroup(results.groups[0]))
            chainer.run().done(callback)
          }],
          groupCategories: ['groups', 'categories', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()

            results.groups.forEach(function (group) {
              chainer.add(group.setCategories(results.categories))
            })

            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          User.findAll({
            include: [
              {model: Group, required: true, include: [
                {model: Category}
              ]}
            ],
            limit: 1
          }).done(function (err, users) {
            expect(err).not.to.be.ok
            expect(users.length).to.equal(1)
            users.forEach(function (user) {
              expect(user.Group).to.be.ok
              expect(user.Group.Categories).to.be.ok
            })
            done()
          })
        })
      })
    })

    it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', function (done) {
      var User = this.sequelize.define('User', {}, {schema: "account"})
        , Group = this.sequelize.define('Group', {
            name: DataTypes.STRING
          }, {schema: "account"})
        , Category = this.sequelize.define('Category', {
            category: DataTypes.STRING
          }, {schema: "account"})

      User.belongsTo(Group, {as: 'Team'})
      Group.hasMany(Category, {as: 'Tags'})

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          groups: function (callback) {
            Group.bulkCreate([
              {name: 'A'},
              {name: 'B'}
            ]).done(function () {
              Group.findAll().done(callback)
            })
          },
          users: function (callback) {
            User.bulkCreate([{}, {}]).done(function () {
              User.findAll().done(callback)
            })
          },
          categories: function (callback) {
            Category.bulkCreate([{}, {}]).done(function () {
              Category.findAll().done(callback)
            })
          },
          userGroups: ['users', 'groups', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()
            chainer.add(results.users[0].setTeam(results.groups[1]))
            chainer.add(results.users[1].setTeam(results.groups[0]))
            chainer.run().done(callback)
          }],
          groupCategories: ['groups', 'categories', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()

            results.groups.forEach(function (group) {
              chainer.add(group.setTags(results.categories))
            })

            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          User.findAll({
            include: [
              {model: Group, required: true, as: 'Team', include: [
                {model: Category, as: 'Tags'}
              ]}
            ],
            limit: 1
          }).done(function (err, users) {
            expect(err).not.to.be.ok
            expect(users.length).to.equal(1)
            users.forEach(function (user) {
              expect(user.Team).to.be.ok
              expect(user.Team.Tags).to.be.ok
            })
            done()
          })
        })
      })
    })

    it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', function (done) {
      var User = this.sequelize.define('User', {}, {schema: "account"})
        , Group = this.sequelize.define('Group', {
            name: DataTypes.STRING
          }, {schema: "account"})
        , Category = this.sequelize.define('Category', {
            category: DataTypes.STRING
          }, {schema: "account"})

      User.belongsTo(Group)
      Group.hasMany(Category)

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          groups: function (callback) {
            Group.bulkCreate([
              {name: 'A'},
              {name: 'B'}
            ]).done(function () {
              Group.findAll().done(callback)
            })
          },
          users: function (callback) {
            User.bulkCreate([{}, {}]).done(function () {
              User.findAll().done(callback)
            })
          },
          categories: function (callback) {
            Category.bulkCreate([{}, {}]).done(function () {
              Category.findAll().done(callback)
            })
          },
          userGroups: ['users', 'groups', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()
            chainer.add(results.users[0].setGroup(results.groups[1]))
            chainer.add(results.users[1].setGroup(results.groups[0]))
            chainer.run().done(callback)
          }],
          groupCategories: ['groups', 'categories', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()

            results.groups.forEach(function (group) {
              chainer.add(group.setCategories(results.categories))
            })

            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          User.findAll({
            include: [
              {model: Group, required: true, include: [
                {model: Category, required: false}
              ]}
            ],
            limit: 1
          }).done(function (err, users) {
            expect(err).not.to.be.ok
            expect(users.length).to.equal(1)
            users.forEach(function (user) {
              expect(user.Group).to.be.ok
              expect(user.Group.Categories).to.be.ok
            })
            done()
          })
        })
      })
    })

    it('should be possible to extend the on clause with a where option on a hasOne include', function (done) {
      var User = this.sequelize.define('User', {}, {schema: "account"})
        , Project = this.sequelize.define('Project', {
            title: DataTypes.STRING
          }, {schema: "account"})

      User.hasOne(Project, {as: 'LeaderOf'})

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          projects: function (callback) {
            Project.bulkCreate([
              {title: 'Alpha'},
              {title: 'Beta'}
            ]).done(function () {
              Project.findAll().done(callback)
            })
          },
          users: function (callback) {
            User.bulkCreate([{}, {}]).done(function () {
              User.findAll().done(callback)
            })
          },
          userProjects: ['users', 'projects', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()
            chainer.add(results.users[1].setLeaderOf(results.projects[1]))
            chainer.add(results.users[0].setLeaderOf(results.projects[0]))
            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          User.findAll({
            include: [
              {model: Project, as: 'LeaderOf', where: {title: 'Beta'}}
            ]
          }).done(function (err, users) {
            expect(err).not.to.be.ok
            expect(users.length).to.equal(1)
            expect(users[0].LeaderOf).to.be.ok
            expect(users[0].LeaderOf.title).to.equal('Beta')
            done()
          })
        })
      })
    })

    it('should be possible to extend the on clause with a where option on a hasMany include with a through model', function (done) {
      var Product = this.sequelize.define('Product', {
            title: DataTypes.STRING
          }, {schema: "account"})
        , Tag = this.sequelize.define('Tag', {
            name: DataTypes.STRING
          }, {schema: "account"})
        , ProductTag = this.sequelize.define('ProductTag', {
            priority: DataTypes.INTEGER
        }, {schema: "account"})

      Product.hasMany(Tag, {through: ProductTag})
      Tag.hasMany(Product, {through: ProductTag})

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          products: function (callback) {
            Product.bulkCreate([
              {title: 'Chair'},
              {title: 'Desk'},
              {title: 'Dress'}
            ]).done(function () {
              Product.findAll().done(callback)
            })
          },
          tags: function(callback) {
            Tag.bulkCreate([
              {name: 'A'},
              {name: 'B'},
              {name: 'C'}
            ]).done(function () {
              Tag.findAll().done(callback)
            })
          },
          productTags: ['products', 'tags', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()

            chainer.add(results.products[0].addTag(results.tags[0], {priority: 1}))
            chainer.add(results.products[0].addTag(results.tags[1], {priority: 2}))

            chainer.add(results.products[1].addTag(results.tags[1], {priority: 1}))

            chainer.add(results.products[2].addTag(results.tags[0], {priority: 3}))
            chainer.add(results.products[2].addTag(results.tags[1], {priority: 1}))
            chainer.add(results.products[2].addTag(results.tags[2], {priority: 2}))

            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          Product.findAll({
            include: [
              {model: Tag, where: {name: 'C'}}
            ]
          }).done(function (err, products) {
            expect(err).not.to.be.ok

            expect(products.length).to.equal(1)
            expect(products[0].Tags.length).to.equal(1)

            done()
          })
        })
      })
    })

    it('should be possible to extend the on clause with a where option on nested includes', function (done) {
      var User = this.sequelize.define('User', {
            name: DataTypes.STRING
          }, {schema: "account"})
        , Product = this.sequelize.define('Product', {
            title: DataTypes.STRING
          }, {schema: "account"})
        , Tag = this.sequelize.define('Tag', {
            name: DataTypes.STRING
          }, {schema: "account"})
        , Price = this.sequelize.define('Price', {
            value: DataTypes.FLOAT
          }, {schema: "account"})
        , Customer = this.sequelize.define('Customer', {
            name: DataTypes.STRING
        }, {schema: "account"})
        , Group = this.sequelize.define('Group', {
            name: DataTypes.STRING
          }, {schema: "account"})
        , GroupMember = this.sequelize.define('GroupMember', {

          }, {schema: "account"})
        , Rank = this.sequelize.define('Rank', {
            name: DataTypes.STRING,
            canInvite: {
              type: DataTypes.INTEGER,
              defaultValue: 0
            },
            canRemove: {
              type: DataTypes.INTEGER,
              defaultValue: 0
            }
          }, {schema: "account"})

      User.hasMany(Product)
      Product.belongsTo(User)

      Product.hasMany(Tag)
      Tag.hasMany(Product)
      Product.belongsTo(Tag, {as: 'Category'})

      Product.hasMany(Price)
      Price.belongsTo(Product)

      User.hasMany(GroupMember, {as: 'Memberships'})
      GroupMember.belongsTo(User)
      GroupMember.belongsTo(Rank)
      GroupMember.belongsTo(Group)
      Group.hasMany(GroupMember, {as: 'Memberships'})

      this.sequelize.sync({force: true}).done(function () {
        var count = 4
          , i = -1

        async.auto({
          groups: function(callback) {
            Group.bulkCreate([
              {name: 'Developers'},
              {name: 'Designers'}
            ]).done(function () {
              Group.findAll().done(callback)
            })
          },
          ranks: function(callback) {
            Rank.bulkCreate([
              {name: 'Admin', canInvite: 1, canRemove: 1},
              {name: 'Member', canInvite: 1, canRemove: 0}
            ]).done(function () {
              Rank.findAll().done(callback)
            })
          },
          tags: function(callback) {
            Tag.bulkCreate([
              {name: 'A'},
              {name: 'B'},
              {name: 'C'}
            ]).done(function () {
              Tag.findAll().done(callback)
            })
          },
          loop: ['groups', 'ranks', 'tags', function (done, results) {
            var groups = results.groups
              , ranks = results.ranks
              , tags = results.tags

            async.whilst(
              function () { return i < count; },
              function (callback) {
                i++

                async.auto({
                  user: function (callback) {
                    User.create({name: 'FooBarzz'}).done(callback)
                  },
                  memberships: ['user', function (callback, results) {
                    GroupMember.bulkCreate([
                      {UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
                      {UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id}
                    ]).done(callback)
                  }],
                  products: function (callback) {
                    Product.bulkCreate([
                      {title: 'Chair'},
                      {title: 'Desk'}
                    ]).done(function () {
                      Product.findAll().done(callback)
                    })
                  },
                  userProducts: ['user', 'products', function (callback, results) {
                    results.user.setProducts([
                      results.products[(i * 2)+0],
                      results.products[(i * 2)+1]
                    ]).done(callback)
                  }],
                  productTags: ['products', function (callback, results) {
                    var chainer = new Sequelize.Utils.QueryChainer()

                    chainer.add(results.products[(i * 2) + 0].setTags([
                      tags[0],
                      tags[2]
                    ]))
                    chainer.add(results.products[(i * 2) + 1].setTags([
                      tags[1]
                    ]))
                    chainer.add(results.products[(i * 2) + 0].setCategory(tags[1]))

                    chainer.run().done(callback)
                  }],
                  prices: ['products', function (callback, results) {
                    Price.bulkCreate([
                      {ProductId: results.products[(i * 2) + 0].id, value: 5},
                      {ProductId: results.products[(i * 2) + 0].id, value: 10},
                      {ProductId: results.products[(i * 2) + 1].id, value: 5},
                      {ProductId: results.products[(i * 2) + 1].id, value: 10},
                      {ProductId: results.products[(i * 2) + 1].id, value: 15},
                      {ProductId: results.products[(i * 2) + 1].id, value: 20}
                    ]).done(callback)
                  }]
                }, callback)
              },
              function (err) {
                expect(err).not.to.be.ok

                User.findAll({
                  include: [
                    {model: GroupMember, as: 'Memberships', include: [
                      Group,
                      {model: Rank, where: {name: 'Admin'}}
                    ]},
                    {model: Product, include: [
                      Tag,
                      {model: Tag, as: 'Category'},
                      {model: Price, where: {
                        value: {
                          gt: 15
                        }
                      }}
                    ]}
                  ],
                  order: [
                    ['User.id', 'ASC']
                  ]
                }).done(function (err, users) {
                  expect(err).not.to.be.ok

                  users.forEach(function (user) {
                    expect(user.Memberships.length).to.equal(1)
                    expect(user.Memberships[0].Rank.name).to.equal('Admin')
                    expect(user.Products.length).to.equal(1)
                    expect(user.Products[0].Prices.length).to.equal(1)
                  })

                  done()
                })
              }
            )
          }]
        }, done)
      })
    })

    it('should be possible to use limit and a where with a belongsTo include', function (done) {
      var User = this.sequelize.define('User', {}, {schema: "account"})
        , Group = this.sequelize.define('Group', {
            name: DataTypes.STRING
          }, {schema: "account"})

      User.belongsTo(Group)

      this.sequelize.sync({force: true}).done(function () {
        async.auto({
          groups: function (callback) {
            Group.bulkCreate([
              {name: 'A'},
              {name: 'B'},
            ]).done(function () {
              Group.findAll().done(callback)
            })
          },
          users: function (callback) {
            User.bulkCreate([{}, {}, {}, {}]).done(function () {
              User.findAll().done(callback)
            })
          },
          userGroups: ['users', 'groups', function (callback, results) {
            var chainer = new Sequelize.Utils.QueryChainer()
            chainer.add(results.users[0].setGroup(results.groups[0]))
            chainer.add(results.users[1].setGroup(results.groups[0]))
            chainer.add(results.users[2].setGroup(results.groups[0]))
            chainer.add(results.users[3].setGroup(results.groups[1]))
            chainer.run().done(callback)
          }]
        }, function (err) {
          expect(err).not.to.be.ok

          User.findAll({
            include: [
              {model: Group, where: {name: 'A'}}
            ],
            limit: 2
          }).done(function (err, users) {
            expect(err).not.to.be.ok
            expect(users.length).to.equal(2)

            users.forEach(function (user) {
              expect(user.Group.name).to.equal('A')
            })
            done()
          })
        })
      })
    })

    it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', function (done) {
      var self = this
      this.fixtureA(function () {
        self.models.Product.findAll({
          attributes: ['title'],
          include: [
            {model: self.models.Company, where: {name: 'NYSE'}},
            {model: self.models.Tag},
            {model: self.models.Price}
          ],
          limit: 3,
          order: [
            [self.models.Product.name+'.id', 'ASC']
          ]
        }).done(function (err, products) {
          expect(err).not.to.be.ok
          expect(products.length).to.equal(3)

          products.forEach(function (product) {
            expect(product.Company.name).to.equal('NYSE')
            expect(product.Tags.length).to.be.ok
            expect(product.Prices.length).to.be.ok
          })
          done()
        })
      })
    })

    it('should be possible to use limit and a where on a hasMany with additional includes', function (done) {
      var self = this
      this.fixtureA(function () {
        self.models.Product.findAll({
          include: [
            {model: self.models.Company},
            {model: self.models.Tag},
            {model: self.models.Price, where: {
              value: {gt: 5}
            }}
          ],
          limit: 6,
          order: [
            ['Product.id', 'ASC']
          ]
        }).done(function (err, products) {
          expect(err).not.to.be.ok
          expect(products.length).to.equal(6)

          products.forEach(function (product) {
            expect(product.Tags.length).to.be.ok
            expect(product.Prices.length).to.be.ok

            product.Prices.forEach(function (price) {
              expect(price.value).to.be.above(5)
            })
          })
          done()
        })
      })
    })

    it('should be possible to use limit and a where on a hasMany with a through model with additional includes', function (done) {
      var self = this
      this.fixtureA(function () {
        self.models.Product.findAll({
          include: [
            {model: self.models.Company},
            {model: self.models.Tag, where: {name: ['A', 'B','C']}},
            {model: self.models.Price}
          ],
          limit: 10,
          order: [
            ['Product.id', 'ASC']
          ]
        }).done(function (err, products) {
          expect(err).not.to.be.ok
          expect(products.length).to.equal(10)

          products.forEach(function (product) {
            expect(product.Tags.length).to.be.ok
            expect(product.Prices.length).to.be.ok

            product.Tags.forEach(function (tag) {
              expect(['A', 'B', 'C']).to.include(tag.name)
            })
          })
          done()
        })
      })
    })

    it.skip('should support including date fields, with the correct timeszone', function (done) {
      var User = this.sequelize.define('user', {
          dateField: Sequelize.DATE
        }, {timestamps: false, schema: "account"})
        , Group = this.sequelize.define('group', {
          dateField: Sequelize.DATE
        }, {timestamps: false, schema: "account"})

      User.hasMany(Group)
      Group.hasMany(User)

      this.sequelize.sync().success(function () {
        User.create({ dateField: Date.UTC(2014, 1, 20) }).success(function (user) {
          Group.create({ dateField: Date.UTC(2014, 1, 20) }).success(function (group) {
            user.addGroup(group).success(function () {
              User.findAll({
                where: {
                  id: user.id
                },
                include: [Group]
              }).success(function (users) {
                expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20))
                expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20))

                done()
              })
            })
          })
        })
      })
    })

  })
})


match = err.message.match(/FOREIGN KEY constraint failed/);

'use strict';

var Utils = require('../../utils')
  , AbstractQuery = require('../abstract/query')
  , DataTypes = require('../../data-types')
  , hstore = require('./hstore')
  , QueryTypes = require('../../query-types')
  , Promise = require('../../promise')
  , sequelizeErrors = require('../../errors.js');

// Parses hstore fields if the model has any hstore fields.
// This cannot be done in the 'pg' lib because hstore is a UDT.
var parseHstoreFields = function(model, row) {
  Utils._.forEach(row, function(value, key) {
    if (model._isHstoreAttribute(key) || (model.attributes[key] && model.attributes[key].type === DataTypes.ARRAY(DataTypes.HSTORE))) {
      row[key] = hstore.parse(value);

      return;
    }

    row[key] = value;
  });
};

module.exports = (function() {
  var Query = function(client, sequelize, callee, options) {
    this.client = client;
    this.sequelize = sequelize;
    this.callee = callee;
    this.options = Utils._.extend({
      logging: console.log,
      plain: false,
      raw: false
    }, options || {});

    this.checkLoggingOption();
  };
  Utils.inherit(Query, AbstractQuery);

  Query.prototype.run = function(sql) {
    /* jshint -W027 */
    this.sql = sql;

    var self = this
      , receivedError = false
      , query = this.client.query(sql)
      , rows = [];

    if (this.options.logging !== false) {
      this.sequelize.log('Executing (' + (this.client.uuid || 'default') + '): ' + this.sql);
    }

    return new Promise(function(resolve, reject) {
      var promise = this;

      query.on('row', function(row) {
        rows.push(row);
      });

      query.on('error', function(err) {
        receivedError = true;
        err.sql = sql;
        promise.emit('sql', sql, self.client.uuid);
        reject(self.formatError(err));
      });

      query.on('end', function(result) {
        if (receivedError) {
          return;
        }

        promise.emit('sql', self.sql, self.client.uuid);
        resolve([rows, sql, result]);
      });
    }).spread(function(rows, sql, result) {
      var results = rows
        , isTableNameQuery = (sql.indexOf('SELECT table_name FROM information_schema.tables') === 0)
        , isRelNameQuery = (sql.indexOf('SELECT relname FROM pg_class WHERE oid IN') === 0);

      if (isTableNameQuery || isRelNameQuery) {
        if (isRelNameQuery) {
          results = rows.map(function(row) {
            return {
              name: row.relname,
              tableName: row.relname.split('_')[0]
            };
          });
        } else {
          results = rows.map(function(row) { return Utils._.values(row); });
        }
        return results;
      }

      if (self.isShowIndexesQuery()) {
        results.forEach(function (result) {
          var attributes = /ON .*? (?:USING .*?\s)?\((.*)\)/gi.exec(result.definition)[1].split(',')
            , field
            , attribute
            , columns;

          // Map column index in table to column name
          columns = Utils._.zipObject(
            result.column_indexes,
            self.sequelize.queryInterface.QueryGenerator.fromArray(result.column_names)
          );
          delete result.column_indexes;
          delete result.column_names;

          // Indkey is the order of attributes in the index, specified by a string of attribute indexes
          result.fields = result.indkey.split(' ').map(function (indKey, index) {
            field = columns[indKey];
            attribute = attributes[index];
            return {
              attribute: field,
              collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined,
              order: attribute.indexOf('DESC') !== -1 ? 'DESC' : attribute.indexOf('ASC') !== -1 ? 'ASC': undefined,
              length: undefined,
            };
          });
          delete result.columns;
        });
        return results;
      } else if (self.isSelectQuery()) {
        if (self.sql.toLowerCase().indexOf('select c.column_name') === 0) {
          result = {};

          rows.forEach(function(_result) {
            result[_result.Field] = {
              type: _result.Type.toUpperCase(),
              allowNull: (_result.Null === 'YES'),
              defaultValue: _result.Default,
              special: (!!_result.special ? self.sequelize.queryInterface.QueryGenerator.fromArray(_result.special) : [])
            };

            if (result[_result.Field].type === 'BOOLEAN') {
              result[_result.Field].defaultValue = { 'false': false, 'true': true }[result[_result.Field].defaultValue];

              if (result[_result.Field].defaultValue === undefined) {
                result[_result.Field].defaultValue = null;
              }
            }

            if (typeof result[_result.Field].defaultValue === 'string') {
              result[_result.Field].defaultValue = result[_result.Field].defaultValue.replace(/'/g, '');

              if (result[_result.Field].defaultValue.indexOf('::') > -1) {
                var split = result[_result.Field].defaultValue.split('::');
                if (split[1].toLowerCase() !== 'regclass)') {
                  result[_result.Field].defaultValue = split[0];
                }
              }
            }
          });

          return result;
        } else {
          // Postgres will treat tables as case-insensitive, so fix the case
          // of the returned values to match attributes
          if (self.options.raw === false && self.sequelize.options.quoteIdentifiers === false) {
            var attrsMap = Utils._.reduce(self.callee.attributes, function(m, v, k) { m[k.toLowerCase()] = k; return m; }, {});
            rows.forEach(function(row) {
              Utils._.keys(row).forEach(function(key) {
                var targetAttr = attrsMap[key];
                if (targetAttr !== key) {
                  row[targetAttr] = row[key];
                  delete row[key];
                }
              });
            });
          }

          if (!!self.callee && !!self.callee._hasHstoreAttributes) {
            rows.forEach(function(row) {
              parseHstoreFields(self.callee, row);
            });
          }

          return self.handleSelectQuery(rows);
        }
      } else if (self.isShowOrDescribeQuery()) {
        return results;
      } else if (QueryTypes.BULKUPDATE === self.options.type) {
        if (!self.options.returning) {
          return result.rowCount;
        }

        if (!!self.callee && !!self.callee._hasHstoreAttributes) {
          rows.forEach(function(row) {
            parseHstoreFields(self.callee, row);
          });
        }

        return self.handleSelectQuery(rows);
      } else if (QueryTypes.BULKDELETE === self.options.type) {
        return result.rowCount;
      } else if (self.isInsertQuery() || self.isUpdateQuery()) {
        if (!!self.callee && self.callee.dataValues) {
          if (!!self.callee.Model && !!self.callee.Model._hasHstoreAttributes) {
            parseHstoreFields(self.callee.Model, rows[0]);
          }

          for (var key in rows[0]) {
            if (rows[0].hasOwnProperty(key)) {
              var record = rows[0][key];

              var attr = Utils._.find(self.callee.Model.rawAttributes, function (attribute) {
                return attribute.fieldName === key || attribute.field === key;
              });

              self.callee.dataValues[attr && attr.fieldName || key] = record;
            }
          }
        }

        return self.callee;
      } else {
        return results;
      }
    });

    return this;
  };

  Query.prototype.formatError = function (err) {
    var match;

    switch (err.code) {
      case '23505':
        match = err.detail.match(/Key \((.*?)\)=\((.*?)\) already exists/);

        return new sequelizeErrors.UniqueConstraintError({
          fields: match[1].split(', '),
          value: match[2].split(', '),
          index: null,
          parent: err
        });

      default:
        return new sequelizeErrors.DatabaseError(err);
    }
  };

  Query.prototype.isShowIndexesQuery = function () {
    return this.sql.indexOf('pg_get_indexdef') !== -1;
  };

  Query.prototype.getInsertIdField = function() {
    return 'id';
  };

  return Query;
})();


'use strict';

var moment = require('moment')
  , path = require('path')
  , Utils = require('./utils')
  , DataTypes = require('./data-types')
  , QueryInterface = require('./query-interface');

module.exports = (function() {
  var Migration = function(migrator, p) {
    this.migrator = migrator;
    this.path = path.resolve(p);
    this.filename = Utils._.last(this.path.split(path.sep));

    var parsed = Migration.parseFilename(this.filename);

    this.migrationId = parsed.id;
    this.date = parsed.date;
    this.queryInterface = this.migrator.queryInterface;
  };

  for (var methodName in QueryInterface.prototype) {
    if (QueryInterface.prototype.hasOwnProperty(methodName) && (typeof QueryInterface.prototype[methodName]) === 'function') {
      (function(methodName) {
        Migration.prototype[methodName] = function() {
          return this.queryInterface[methodName].apply(this.queryInterface, arguments);
        };
      })(methodName);
    }
  }

  ///////////////
  // static /////
  ///////////////

  Migration.parseFilename = function(s) {
    var matches = s.match(/^((\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}))[-_].+/);

    if (matches === null) {
      throw new Error(s + ' is not a valid migration name! Use YYYYMMDDHHmmss-migration-name format.');
    }

    return {
      id: parseInt(matches[1], 10),
      date: moment(matches.slice(2, 8).join('-'), 'YYYYMMDDHHmmss')
    };
  };

  ///////////////
  // member /////
  ///////////////

  Object.defineProperty(Migration.prototype, 'migration', {
    get: function() {
      if (this.path.match(/\.coffee$/)) {
        try {
          // 1.7.x compiler registration
          require('coffee-script/register');
        } catch (e) {
          try {
            // Prior to 1.7.x compiler registration
            require('coffee-script');
          } catch (e) {
            console.log('You have to add \"coffee-script\" to your package.json.');
            process.exit(1);
          }
        }
      }

      return require(this.path);
    }
  });

  Migration.prototype.execute = function(options) {
    return new Utils.CustomEventEmitter(function(emitter) {
      options = Utils._.extend({
        method: 'up'
      }, options || {});

      this.migration[options.method].call(null, this, DataTypes, function(err) {
        if (err) {
          emitter.emit('error', err);
        } else {
          emitter.emit('success', null);
        }
      });
    }.bind(this)).run();
  };

  Migration.prototype.isBefore = function(dateString, options) {
    options = Utils._.extend({
      withoutEquals: false
    }, options || {});

    var date = Migration.parseFilename(dateString.toString() + '-foo.js').date;

    return options.withoutEqual ? (date > this.date) : (date >= this.date);
  };

  Migration.prototype.isAfter = function(dateString, options) {
    options = Utils._.extend({
      withoutEquals: false
    }, options || {});

    var date = Migration.parseFilename(dateString.toString() + '-foo.js').date;

    return options.withoutEqual ? (date < this.date) : (date <= this.date);
  };

  return Migration;
})();


/*jslint node:true */
"use strict";

var Sequelize = require('sequelize');

// initialize database connection
var sequelize = new Sequelize('testsequelize', 'testsequelize', 'testsequelize', {
    dialect: 'postgres',
    port: 5432,
    define: {
        freezeTableName: true
    }
});

// load models
var models = [
    'Trainer',
    'Series',
    'Video'
];

models.forEach(function(model) {
    module.exports[model] = sequelize.import(__dirname + '/' + model);
});

// describe relationships
(function(m) {
    m.Series.hasOne(m.Video);
    m.Trainer.hasMany(m.Series);
})(module.exports);

// export connection
module.exports.sequelize = sequelize;