public function save()
 {
     $this->_init();
     $this->_process_filters(SRECORD_FILTER_BEFORE_SAVE);
     $was_new = $this->is_new();
     if (!$this->is_dirty()) {
         $this->_process_filters(SRECORD_FILTER_AFTER_SAVE, array($was_new));
         return;
     }
     if (!$was_new) {
         $fields = '';
         $keyname = $this->_db_key;
         foreach ($this->_db_fields as $k => $dummy) {
             if ($k == $keyname) {
                 continue;
             }
             $fields .= ($fields == '' ? '' : ',') . "@_f_{$k}=@_v_{$k}";
         }
         $cmd = new SDBCommand("UPDATE @_db_table SET {$fields} WHERE @_fk_id=@_k_id LIMIT 1");
         $cmd->set('_db_table', $this->_db_table, SDB::TableName);
         $cmd->set('_fk_id', $this->_db_key, SDB::FieldName);
         $cmd->set('_k_id', $this->{$keyname}, SDB::Int);
         foreach ($this->_db_fields as $k => $ts) {
             if ($k == $keyname) {
                 continue;
             }
             $cmd->set("_f_{$k}", $ts['f'], SDB::FieldName);
             $cmd->set("_v_{$k}", $this->{$k}, $ts['t'], $ts['s']);
         }
         if (!$cmd->execute()) {
             // is_new can return invalid values, if PK is not autoincremented, and PK is not null
             // in such cases update will be executed instead of insert
             // so if update failed, try to insert instead
             // *but* update will also failed, if no fields is updated and _use_dirty is false
             // so double-check for new record
             $cmd = new SDBCommand("SELECT @_fk_id FROM @_db_table WHERE @_fk_id=@_k_id LIMIT 1");
             $cmd->set('_db_table', $this->_db_table, SDB::TableName);
             $cmd->set('_fk_id', $this->_db_key, SDB::FieldName);
             $cmd->set('_k_id', $this->{$keyname}, SDB::Int);
             $was_new = $cmd->get_row() == null;
         }
     }
     if ($was_new) {
         $fields = '';
         $values = '';
         foreach ($this->_db_fields as $k => $dummy) {
             if ($k == $this->_db_key && $this->{$k} === null) {
                 continue;
             }
             $fields .= ($fields == '' ? '' : ',') . "@_f_{$k}";
             $values .= ($values == '' ? '' : ',') . "@{$k}";
         }
         $cmd = new SDBCommand("INSERT INTO @_db_table ({$fields}) VALUES ({$values})");
         $cmd->set('_db_table', $this->_db_table, SDB::TableName);
         foreach ($this->_db_fields as $k => $ts) {
             if ($k == $this->_db_key && $this->{$k} === null) {
                 continue;
             }
             $cmd->set("_f_{$k}", $ts['f'], SDB::FieldName);
             $cmd->set($k, $this->{$k}, $ts['t'], $ts['s']);
         }
         $keyname = $this->_db_key;
         $this->{$keyname} = $cmd->insert();
     }
     if ($this->_use_dirty) {
         foreach ($this->_db_fields as $prop => $ts) {
             $this->_db_prev_value[$prop] = $this->{$prop};
         }
     }
     $this->_process_filters(SRECORD_FILTER_AFTER_SAVE, array($was_new));
 }