예제 #1
0
    def set_collection(self, user_id, collection_name, **values):
        """Creates a collection"""
        # XXX values is not used for now because there are no values besides
        # the name
        if self.collection_exists(user_id, collection_name):
            return

        values['userid'] = user_id
        values['name'] = collection_name

        if self.standard_collections:
            ids = _STANDARD_COLLECTIONS.keys()
            min_id = max(ids) + 1
        else:
            min_id = 0

        # getting the max collection_id
        # XXX why don't we have an autoinc here ?
        # see https://bugzilla.mozilla.org/show_bug.cgi?id=579096
        next_id = -1
        while next_id < min_id:
            query = self._get_query('COLLECTION_NEXTID', user_id)
            max_ = safe_execute(self._engine, query, user_id=user_id).first()
            if max_[0] is None:
                next_id = min_id
            else:
                next_id = max_[0] + 1

        # insertion
        values['collectionid'] = next_id
        query = insert(collections).values(**values)
        safe_execute(self._engine, query, **values)
        return next_id
예제 #2
0
 def delete_storage(self, user_id):
     """Removes all user data"""
     for query in ('DELETE_USER_COLLECTIONS', 'DELETE_USER_WBOS'):
         query = self._get_query(query, user_id)
         safe_execute(self._engine, query, user_id=user_id)
     # XXX see if we want to check the rowcount
     return True
예제 #3
0
    def set_user(self, user_id, **values):
        """set information for a user. values contains the fields to set.

        If the user doesn't exists, it will be created.
        """
        values['id'] = user_id
        if not self.user_exists(user_id):
            query = insert(users).values(**values)
        else:
            query = update(users).where(users.c.id == user_id)
            query = query.values(**values)

        safe_execute(self._engine, query)
    def get_user_info(self, user, attrs):
        """Returns user info

        Args:
            user: the user object
            attrs: the pieces of data requested

        Returns:
            user object populated with attrs
        """
        user_id = self.get_user_id(user)
        if user_id is None:
            return user

        attrs = [attr for attr in attrs if not user.get(attr)]
        if attrs == []:
            return user

        fields = []
        for attr in attrs:
            fields.append(getattr(users.c, attr))

        _USER_INFO = select(fields, users.c.userid == bindparam('user_id'))
        res = safe_execute(self._engine, _USER_INFO,
                           user_id=user_id).fetchone()
        if res is None:
            return user
        for attr in attrs:
            try:
                user[attr] = getattr(res, attr)
            except AttributeError:
                user[attr] = None

        return user
예제 #5
0
    def clear_reset_code(self, user_id):
        if self._engine is None:
            raise NotImplementedError()

        query = delete(reset_codes).where(reset_codes.c.username == user_id)
        res = safe_execute(self._engine, query)
        return res.rowcount > 0
예제 #6
0
파일: sql.py 프로젝트: rfk/moz-server-core
    def get_user_info(self, user, attrs):
        """Returns user info

        Args:
            user: the user object
            attrs: the pieces of data requested

        Returns:
            user object populated with attrs
        """
        user_id = self.get_user_id(user)
        if user_id is None:
            return user

        attrs = [attr for attr in attrs if not user.get(attr)]
        if attrs == []:
            return user

        fields = []
        for attr in attrs:
            fields.append(getattr(users.c, attr))

        _USER_INFO = select(fields, users.c.userid == bindparam('user_id'))
        res = safe_execute(self._engine, _USER_INFO,
                           user_id=user_id).fetchone()
        if res is None:
            return user
        for attr in attrs:
            try:
                user[attr] = getattr(res, attr)
            except AttributeError:
                user[attr] = None

        return user
예제 #7
0
    def update_password(self, user_id, password, old_password=None, key=None):
        """Change the user password

        Args:
            user_id: user id
            password: new password

        Returns:
            True if the change was successful, False otherwise
        """
        if old_password is None:
            if key:
                #using a key, therefore we should check it
                if self._get_reset_code(user_id) == key:
                    self.clear_reset_code(user_id)
                else:
                    logger.error("bad key used for update password")
                    return False
            else:
                return False

        password_hash = ssha256(password)
        query = update(users).where(users.c.id == user_id)
        res = safe_execute(self._engine,
                           query.values(password_hash=password_hash))
        return res.rowcount == 1
예제 #8
0
    def get_collection(self, user_id, collection_name, fields=None,
                       create=True):
        """Return information about a collection."""
        if fields is None:
            fields = [collections]
            field_names = collections.columns.keys()
        else:
            field_names = fields
            fields = [getattr(collections.c, field) for field in fields]

        query = select(fields, and_(collections.c.userid == user_id,
                                    collections.c.name == collection_name))
        res = safe_execute(self._engine, query).first()

        # the collection is created
        if res is None and create:
            collid = self.set_collection(user_id, collection_name)
            res = {'userid': user_id, 'collectionid': collid,
                   'name': collection_name}
            if fields is not None:
                for key in res.keys():
                    if key not in field_names:
                        del res[key]
        else:
            # make this a single step
            res = dict([(key, value) for key, value in res.items()
                         if value is not None])
        return res
    def create_user(self, username, password, email, **extra_fields):
        """Creates a user. Returns True on success."""
        if not self.allow_new_users:
            raise BackendError("Creation of new users is disabled")

        password_hash = sscrypt(password)
        values = {
            'username': username,
            'password': password_hash,
            'mail': email,
        }
        for field in ('userid', 'accountStatus', 'mailVerified', 'syncNode'):
            if field in extra_fields:
                values[field] = extra_fields[field]
        query = insert(users).values(**values)
        try:
            res = safe_execute(self._engine, query)
        except IntegrityError:
            #Name already exists
            return False

        if res.rowcount != 1:
            return False

        #need a copy with some of the info for the return value
        userobj = User()
        userobj['username'] = username
        userobj['userid'] = res.lastrowid
        userobj['mail'] = email

        return userobj
예제 #10
0
 def get_user_id(self, user_name):
     """Returns the id for a user name"""
     user = safe_execute(self._engine, _USER_ID,
                         user_name=user_name).fetchone()
     if user is None:
         return None
     return user.id
예제 #11
0
 def get_user_id(self, user_name):
     """Returns the id for a user name"""
     user = safe_execute(self._engine, _USER_ID,
                         user_name=user_name).fetchone()
     if user is None:
         return None
     return user.id
예제 #12
0
 def create_user(self, user_name, password, email):
     """Creates a user. Returns True on success."""
     password_hash = ssha256(password)
     query = insert(users).values(username=user_name, email=email,
                                  password_hash=password_hash, status=1)
     res = safe_execute(self._engine, query)
     return res.rowcount == 1
    def clear_reset_code(self, user_id):
        if self._engine is None:
            raise NotImplementedError()

        query = delete(reset_codes).where(reset_codes.c.username == user_id)
        res = safe_execute(self._engine, query)
        return res.rowcount > 0
예제 #14
0
파일: sql.py 프로젝트: mozilla/server-core
    def update_password(self, user_id, password, old_password=None, key=None):
        """Change the user password

        Args:
            user_id: user id
            password: new password

        Returns:
            True if the change was successful, False otherwise
        """
        if old_password is None:
            if key:
                #using a key, therefore we should check it
                if self._get_reset_code(user_id) == key:
                    self.clear_reset_code(user_id)
                else:
                    logger.error("bad key used for update password")
                    return False
            else:
                return False

        password_hash = ssha256(password)
        query = update(users).where(users.c.id == user_id)
        res = safe_execute(self._engine,
                           query.values(password_hash=password_hash))
        return res.rowcount == 1
예제 #15
0
 def collection_exists(self, user_id, collection_name):
     """Returns True if the collection exists"""
     query = self._get_query('COLLECTION_EXISTS', user_id)
     res = safe_execute(self._engine, query, user_id=user_id,
                        collection_name=collection_name)
     res = res.fetchone()
     return res is not None
예제 #16
0
 def clear_reset_code(self, user_id):
     query = update(users).where(users.c.id == user_id)
     code = expiration = None
     res = safe_execute(
         self._engine,
         query.values(id=user_id, reset=code, reset_expiration=expiration))
     return res.rowcount == 1
예제 #17
0
파일: sql.py 프로젝트: mozilla/server-core
 def _set_reset_code(self, user_id):
     code, expiration = generate_reset_code()
     query = update(users).values(reset=code, reset_expiration=expiration)
     res = safe_execute(self._engine, query.where(users.c.id == user_id))
     if res.rowcount != 1:
         logger.debug('Unable to add a new reset code')
         return None  # XXX see if appropriate
     return code
예제 #18
0
 def _set_reset_code(self, user_id):
     code, expiration = generate_reset_code()
     query = update(users).values(reset=code, reset_expiration=expiration)
     res = safe_execute(self._engine, query.where(users.c.id == user_id))
     if res.rowcount != 1:
         logger.debug('Unable to add a new reset code')
         return None  # XXX see if appropriate
     return code
예제 #19
0
 def delete_item(self, user_id, collection_name, item_id,
                 storage_time=None):
     """Deletes an item"""
     collection_id = self._get_collection_id(user_id, collection_name)
     query = self._get_query('DELETE_SOME_USER_WBO', user_id)
     res = safe_execute(self._engine, query, user_id=user_id,
                        collection_id=collection_id,
                        item_id=item_id)
     return res.rowcount == 1
예제 #20
0
 def create_user(self, user_name, password, email):
     """Creates a user. Returns True on success."""
     password_hash = ssha256(password)
     query = insert(users).values(username=user_name,
                                  email=email,
                                  password_hash=password_hash,
                                  status=1)
     res = safe_execute(self._engine, query)
     return res.rowcount == 1
예제 #21
0
    def get_collections(self, user_id, fields=None):
        """returns the collections information """
        if fields is None:
            fields = [collections]
        else:
            fields = [getattr(collections.c, field) for field in fields]

        query = select(fields, collections.c.userid == user_id)
        return safe_execute(self._engine, query).fetchall()
 def _set_reset_code(self, user_id):
     rc = ResetCode()
     code = rc._generate_reset_code()
     expiration = datetime.datetime.now() + datetime.timedelta(hours=6)
     query = update(users).values(reset=code, reset_expiration=expiration)
     res = safe_execute(self._engine, query.where(users.c.id == user_id))
     if res.rowcount != 1:
         self.logger.debug('Unable to add a new reset code')
         return None  # XXX see if appropriate
     return code
예제 #23
0
 def _set_reset_code(self, user_id):
     rc = ResetCode()
     code = rc._generate_reset_code()
     expiration = datetime.datetime.now() + datetime.timedelta(hours=6)
     query = update(users).values(reset=code, reset_expiration=expiration)
     res = safe_execute(self._engine, query.where(users.c.id == user_id))
     if res.rowcount != 1:
         self.logger.debug('Unable to add a new reset code')
         return None  # XXX see if appropriate
     return code
예제 #24
0
 def get_collection_timestamps(self, user_id):
     """return the collection names for a given user"""
     query = 'COLLECTION_STAMPS'
     query = self._get_query(query, user_id)
     res = safe_execute(self._engine, query, user_id=user_id)
     try:
         return dict([(self._collid2name(user_id, coll_id),
                     bigint2time(stamp)) for coll_id, stamp in res])
     finally:
         self._purge_cache(user_id)
예제 #25
0
 def get_collection_counts(self, user_id):
     """Return the collection counts for a given user"""
     query = self._get_query('COLLECTION_COUNTS', user_id)
     res = safe_execute(self._engine, query, user_id=user_id,
                        ttl=_int_now())
     try:
         return dict([(self._collid2name(user_id, collid), count)
                       for collid, count in res])
     finally:
         self._purge_cache(user_id)
예제 #26
0
 def item_exists(self, user_id, collection_name, item_id):
     """Returns a timestamp if an item exists."""
     collection_id = self._get_collection_id(user_id, collection_name)
     query = self._get_query('ITEM_EXISTS', user_id)
     res = safe_execute(self._engine, query, user_id=user_id,
                        item_id=item_id,
                        collection_id=collection_id, ttl=_int_now())
     res = res.fetchone()
     if res is None:
         return None
     return bigint2time(res[0])
예제 #27
0
 def get_collection_max_timestamp(self, user_id, collection_name):
     """Returns the max timestamp of a collection."""
     query = self._get_query('COLLECTION_MAX_STAMPS', user_id)
     collection_id = self._get_collection_id(user_id, collection_name)
     res = safe_execute(self._engine, query, user_id=user_id,
                        collection_id=collection_id)
     res = res.fetchone()
     stamp = res[0]
     if stamp is None:
         return None
     return bigint2time(stamp)
예제 #28
0
    def delete_collection(self, user_id, collection_name):
        """deletes a collection"""
        if not self.collection_exists(user_id, collection_name):
            return

        # removing items first
        self.delete_items(user_id, collection_name)

        # then the collection
        query = self._get_query('DELETE_USER_COLLECTION', user_id)
        return safe_execute(self._engine, query, user_id=user_id,
                            collection_name=collection_name)
예제 #29
0
    def get_user(self, user_id, fields=None):
        """Returns user information.

        If fields is provided, it is a list of fields to return
        """
        if fields is None:
            fields = [users]
        else:
            fields = [getattr(users.c, field) for field in fields]

        query = select(fields, users.c.id == user_id)
        return safe_execute(self._engine, query).first()
예제 #30
0
    def get_total_size(self, user_id, recalculate=False):
        """Returns the total size in KB of a user storage.

        The size is the sum of stored payloads.
        """
        query = self._get_query('USER_STORAGE_SIZE', user_id)
        res = safe_execute(self._engine, query, user_id=user_id,
                           ttl=_int_now())
        res = res.fetchone()
        if res is None or res[0] is None:
            return 0.0
        return int(res[0]) / _KB
예제 #31
0
    def delete_items(self, user_id, collection_name, item_ids=None,
                     filters=None, limit=None, offset=None, sort=None,
                     storage_time=None):
        """Deletes items. All items are removed unless item_ids is provided"""
        collection_id = self._get_collection_id(user_id, collection_name)
        wbo = self._get_wbo_table(user_id)
        query = _delete(wbo)
        where = [wbo.c.username == bindparam('user_id'),
                 wbo.c.collection == bindparam('collection_id')]

        if item_ids is not None:
            where.append(wbo.c.id.in_(item_ids))

        if filters is not None:
            for field, value in filters.items():
                field = getattr(wbo.c, field)

                operator, value = value
                if field.name == 'modified':
                    value = _roundedbigint(value)
                if isinstance(value, (list, tuple)):
                    where.append(field.in_(value))
                else:
                    if operator == '=':
                        where.append(field == value)
                    elif operator == '<':
                        where.append(field < value)
                    elif operator == '>':
                        where.append(field > value)

        where = and_(*where)
        query = query.where(where)

        if self.engine_name != 'sqlite':
            if sort is not None:
                if sort == 'oldest':
                    query = query.order_by(wbo.c.modified.asc())
                elif sort == 'newest':
                    query = query.order_by(wbo.c.modified.desc())
                else:
                    query = query.order_by(wbo.c.sortindex.desc())

            if limit is not None and int(limit) > 0:
                query = query.limit(int(limit))

            if offset is not None and int(offset) > 0:
                query = query.offset(int(offset))

        # XXX see if we want to send back more details
        # e.g. by checking the rowcount
        res = safe_execute(self._engine, query, user_id=user_id,
                           collection_id=collection_id)
        return res.rowcount > 0
예제 #32
0
    def _set_item(self, user_id, collection_name, item_id, **values):
        """Adds or update an item"""
        wbo = self._get_wbo_table(user_id)

        if 'modified' in values:
            values['modified'] = _roundedbigint(values['modified'])

        if 'ttl' not in values:
            values['ttl'] = MAX_TTL
        else:
            # ttl is provided in seconds, so we add it
            # to the current timestamp
            values['ttl'] += _int_now()

        modified = self.item_exists(user_id, collection_name, item_id)

        if 'payload' in values:
            values['payload_size'] = len(values['payload'])

        collection_id = self._get_collection_id(user_id,
                                                collection_name)

        if modified is None:   # does not exists
            values['collection'] = collection_id
            values['id'] = item_id
            values['username'] = user_id
            query = insert(wbo).values(**values)
        else:
            if 'id' in values:
                del values['id']
            key = and_(wbo.c.id == item_id, wbo.c.username == user_id,
                       wbo.c.collection == collection_id)
            query = update(wbo).where(key).values(**values)

        safe_execute(self._engine, query)

        if 'modified' in values:
            return bigint2time(values['modified'])

        return modified
예제 #33
0
파일: sql.py 프로젝트: rfk/moz-server-core
    def authenticate_user(self, user_name, password):
        """Authenticates a user given a user_name and password.

        Returns the user id in case of success. Returns None otherwise."""
        user = safe_execute(self._engine, _USER_AUTH, user_name=user_name).fetchone()
        if user is None:
            return None

        if user.status != 1:  # user is disabled
            return None

        if validate_password(password, user.password_hash):
            return user.id
예제 #34
0
    def get_collection_sizes(self, user_id):
        """Returns the total size in KB for each collection of a user storage.

        The size is the sum of stored payloads.
        """
        query = self._get_query('COLLECTIONS_STORAGE_SIZE', user_id)
        res = safe_execute(self._engine, query, user_id=user_id,
                           ttl=_int_now())
        try:
            return dict([(self._collid2name(user_id, col[0]),
                        int(col[1]) / _KB) for col in res])
        finally:
            self._purge_cache(user_id)
예제 #35
0
    def update_email(self, user_id, email, password=None):
        """Change the user e-mail

        Args:
            user_id: user id
            email: new email

        Returns:
            True if the change was successful, False otherwise
        """
        query = update(users).where(users.c.id == user_id)
        res = safe_execute(self._engine, query.values(email=email))
        return res.rowcount == 1
예제 #36
0
파일: sql.py 프로젝트: rfk/moz-server-core
    def delete_user(self, user_id, password=None):
        """Deletes a user

        Args:
            user_id: user id
            password: user password, if needed

        Returns:
            True if the deletion was successful, False otherwise
        """
        if password is not None:
            # we want to control if the password is good
            user = safe_execute(self._engine, _USER_PASSWORD, user_id=user_id).fetchone()
            if user is None:
                return False

            if not validate_password(password, user.password_hash):
                return False

        query = delete(users).where(users.c.id == user_id)
        res = safe_execute(self._engine, query)
        return res.rowcount == 1
예제 #37
0
    def update_email(self, user_id, email, password=None):
        """Change the user e-mail

        Args:
            user_id: user id
            email: new email

        Returns:
            True if the change was successful, False otherwise
        """
        query = update(users).where(users.c.id == user_id)
        res = safe_execute(self._engine, query.values(email=email))
        return res.rowcount == 1
예제 #38
0
파일: sql.py 프로젝트: rfk/moz-server-core
    def admin_update_field(self, user, key, value):
        """Change the value of a user's field using an admin bind
        True if the change was successful, False otherwise

        The sql model doesn't have the concept of different permissions,
        """
        user_id = self.get_user_id(user)
        if user_id is None:
            return False

        query = update(users, users.c.userid == user_id, {key: value})
        res = safe_execute(self._engine, query)
        user[key] = value
        return res.rowcount == 1
예제 #39
0
    def authenticate_user(self, user_name, password):
        """Authenticates a user given a user_name and password.

        Returns the user id in case of success. Returns None otherwise."""
        user = safe_execute(self._engine, _USER_AUTH,
                            user_name=user_name).fetchone()
        if user is None:
            return None

        if user.status != 1:  # user is disabled
            return None

        if validate_password(password, user.password_hash):
            return user.id
    def admin_update_field(self, user, key, value):
        """Change the value of a user's field using an admin bind
        True if the change was successful, False otherwise

        The sql model doesn't have the concept of different permissions,
        """
        user_id = self.get_user_id(user)
        if user_id is None:
            return False

        query = update(users, users.c.userid == user_id, {key: value})
        res = safe_execute(self._engine, query)
        user[key] = value
        return res.rowcount == 1
예제 #41
0
파일: sql.py 프로젝트: rfk/moz-server-core
    def get_user_info(self, user_id):
        """Returns user info

        Args:
            user_id: user id

        Returns:
            tuple: username, email
        """
        res = safe_execute(self._engine, _USER_INFO, user_id=user_id).fetchone()
        if res is None:
            return None, None

        return res.username, res.email
예제 #42
0
    def delete_user(self, user_id, password=None):
        """Deletes a user

        Args:
            user_id: user id
            password: user password, if needed

        Returns:
            True if the deletion was successful, False otherwise
        """
        if password is not None:
            # we want to control if the password is good
            user = safe_execute(self._engine, _USER_PASSWORD,
                                user_id=user_id).fetchone()
            if user is None:
                return False

            if not validate_password(password, user.password_hash):
                return False

        query = delete(users).where(users.c.id == user_id)
        res = safe_execute(self._engine, query)
        return res.rowcount == 1
예제 #43
0
    def get_user_info(self, user_id):
        """Returns user info

        Args:
            user_id: user id

        Returns:
            tuple: username, email
        """
        res = safe_execute(self._engine, _USER_INFO,
                           user_id=user_id).fetchone()
        if res is None:
            return None, None

        return res.username, res.email
    def get_user_id(self, user):
        """Returns the id for a user name"""
        user_id = user.get('userid')
        if user_id is not None:
            return user_id

        username = user['username']
        if username is None:
            return None

        res = safe_execute(self._engine, _USER_ID,
                           username=username).fetchone()
        if res is None:
            return None
        user['userid'] = res.userid
        return res.userid
    def _set_reset_code(self, user_id):
        code, expiration = generate_reset_code()
        query = delete(reset_codes).where(reset_codes.c.username == user_id)
        self._engine.execute(query)

        query = insert(reset_codes).values(reset=code,
                                           expiration=expiration,
                                           username=user_id)

        res = safe_execute(self._engine, query)

        if res.rowcount != 1:
            logger.debug('Unable to add a new reset code in the'
                         ' reset_code table')
            return None  # XXX see if appropriate

        return code
    def update_password(self, user_id, password, old_password):
        """Change the user password

        Args:
            user_id: user id
            password: new password
            old_password: the old password

        Returns:
            True if the change was successful, False otherwise
        """
        # XXX check the old password
        #
        password_hash = ssha256(password)
        query = update(users).where(users.c.id == user_id)
        res = safe_execute(self._engine,
                           query.values(password_hash=password_hash))
        return res.rowcount == 1
예제 #47
0
    def _set_reset_code(self, user_id):
        code = self._generate_reset_code()
        query = delete(reset_codes).where(
            and_(reset_codes.c.username == user_id,
                 reset_codes.c.product == self.product))
        self._engine.execute(query)

        expiration_time = datetime.datetime.now() + \
                            datetime.timedelta(seconds=self.expiration)

        query = insert(reset_codes).values(reset=code,
                                           expiration=expiration_time,
                                           product=self.product,
                                           username=user_id)

        res = safe_execute(self._engine, query)

        if res.rowcount != 1:
            raise BackendError('adding a reset code to the reset table failed')

        return code
    def authenticate_user(self, user, credentials, attrs=None):
        """Authenticates a user given a user object and credentials.

        Returns the user id in case of success. Returns None otherwise.
        """

        username = credentials.get("username")
        if username is None:
            return None
        if user.get("username") not in (None, username):
            return None

        password = credentials.get("password")
        if password is None:
            return None

        fields = [users.c.userid, users.c.password, users.c.accountStatus]
        if attrs is not None:
            for attr in attrs:
                fields.append(getattr(users.c, attr))
        else:
            attrs = []

        _USER_AUTH = select(fields, users.c.username == bindparam('username'))
        res = safe_execute(self._engine, _USER_AUTH,
                           username=username).fetchone()
        if res is None:
            return None

        if self.check_account_state and res.accountStatus != 1:
            return None

        if not validate_password(password, res.password):
            return None

        user['username'] = username
        user['userid'] = res.userid
        for attr in attrs:
            user[attr] = getattr(res, attr)
        return res.userid
    def delete_user(self, user, credentials=None):
        """
        Deletes a user. Needs to be done with admin privileges, since
        users don't have permission to do it themselves.

        Args:
            user: the user object

        Returns:
            True if the deletion was successful, False otherwise
        """

        if credentials is not None:
            if not self.authenticate_user(user, credentials):
                return False

        user_id = self.get_user_id(user)
        if user_id is None:
            return False

        query = delete(users).where(users.c.userid == user_id)
        res = safe_execute(self._engine, query)
        return res.rowcount == 1
 def _safe_execute(self, *args, **kwds):
     return safe_execute(self.auth._engine, *args, **kwds)
예제 #51
0
 def _get_username(self, uid):
     """Returns the id for a user name"""
     user = safe_execute(self._engine, _USER_NAME, uid=uid).fetchone()
     if user is None:
         return None
     return user.username
예제 #52
0
 def _delete_reset_code(self, user_id):
     query = delete(reset_codes).where(reset_codes.c.username == user_id)
     res = safe_execute(self._engine, query)
     return res.rowcount > 0