mirror of
https://github.com/servo/servo.git
synced 2025-06-25 09:34:32 +01:00
There were a lot of crashing IndexedDB tests caused by code in DOMStringList that required a Window object despite being exposed to non-Window globals. Testing: Lots of existing tests no longer crash. Fixes: part of #6963 Signed-off-by: Josh Matthews <josh@joshmatthews.net>
588 lines
22 KiB
Rust
588 lines
22 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use std::ptr;
|
|
|
|
use dom_struct::dom_struct;
|
|
use js::conversions::ToJSValConvertible;
|
|
use js::jsapi::{
|
|
ESClass, GetBuiltinClass, IsArrayBufferObject, JS_DeleteUCProperty,
|
|
JS_GetOwnUCPropertyDescriptor, JS_GetStringLength, JS_IsArrayBufferViewObject, JSObject,
|
|
ObjectOpResult, ObjectOpResult_SpecialCodes, PropertyDescriptor,
|
|
};
|
|
use js::jsval::UndefinedValue;
|
|
use js::rust::{HandleValue, MutableHandleValue};
|
|
use net_traits::IpcSend;
|
|
use net_traits::indexeddb_thread::{
|
|
AsyncOperation, IndexedDBKeyType, IndexedDBThreadMsg, SyncOperation,
|
|
};
|
|
use profile_traits::ipc;
|
|
|
|
use crate::dom::bindings::cell::DomRefCell;
|
|
use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
|
|
use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBObjectStoreMethods;
|
|
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
|
|
// We need to alias this name, otherwise test-tidy complains at &String reference.
|
|
use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
|
|
use crate::dom::bindings::error::{Error, Fallible};
|
|
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
|
|
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
|
use crate::dom::bindings::str::DOMString;
|
|
use crate::dom::bindings::structuredclone;
|
|
use crate::dom::domstringlist::DOMStringList;
|
|
use crate::dom::globalscope::GlobalScope;
|
|
use crate::dom::idbrequest::IDBRequest;
|
|
use crate::dom::idbtransaction::IDBTransaction;
|
|
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
|
|
|
|
#[derive(JSTraceable, MallocSizeOf)]
|
|
pub enum KeyPath {
|
|
String(DOMString),
|
|
StringSequence(Vec<DOMString>),
|
|
}
|
|
|
|
#[dom_struct]
|
|
pub struct IDBObjectStore {
|
|
reflector_: Reflector,
|
|
name: DomRefCell<DOMString>,
|
|
key_path: Option<KeyPath>,
|
|
index_names: DomRoot<DOMStringList>,
|
|
transaction: MutNullableDom<IDBTransaction>,
|
|
auto_increment: bool,
|
|
|
|
// We store the db name in the object store to be able to find the correct
|
|
// store in the idb thread when checking if we have a key generator
|
|
db_name: DOMString,
|
|
}
|
|
|
|
impl IDBObjectStore {
|
|
pub fn new_inherited(
|
|
global: &GlobalScope,
|
|
db_name: DOMString,
|
|
name: DOMString,
|
|
options: Option<&IDBObjectStoreParameters>,
|
|
can_gc: CanGc,
|
|
) -> IDBObjectStore {
|
|
let key_path: Option<KeyPath> = match options {
|
|
Some(options) => options.keyPath.as_ref().map(|path| match path {
|
|
StrOrStringSequence::String(inner) => KeyPath::String(inner.clone()),
|
|
StrOrStringSequence::StringSequence(inner) => {
|
|
KeyPath::StringSequence(inner.clone())
|
|
},
|
|
}),
|
|
None => None,
|
|
};
|
|
|
|
IDBObjectStore {
|
|
reflector_: Reflector::new(),
|
|
name: DomRefCell::new(name),
|
|
key_path,
|
|
|
|
index_names: DOMStringList::new(global, Vec::new(), can_gc),
|
|
transaction: Default::default(),
|
|
// FIXME:(arihant2math)
|
|
auto_increment: false,
|
|
|
|
db_name,
|
|
}
|
|
}
|
|
|
|
pub fn new(
|
|
global: &GlobalScope,
|
|
db_name: DOMString,
|
|
name: DOMString,
|
|
options: Option<&IDBObjectStoreParameters>,
|
|
can_gc: CanGc,
|
|
) -> DomRoot<IDBObjectStore> {
|
|
reflect_dom_object(
|
|
Box::new(IDBObjectStore::new_inherited(
|
|
global, db_name, name, options, can_gc,
|
|
)),
|
|
global,
|
|
can_gc,
|
|
)
|
|
}
|
|
|
|
pub fn get_name(&self) -> DOMString {
|
|
self.name.borrow().clone()
|
|
}
|
|
|
|
pub fn set_transaction(&self, transaction: &IDBTransaction) {
|
|
self.transaction.set(Some(transaction));
|
|
}
|
|
|
|
pub fn transaction(&self) -> Option<DomRoot<IDBTransaction>> {
|
|
self.transaction.get()
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#valid-key-path
|
|
pub fn is_valid_key_path(key_path: &StrOrStringSequence) -> bool {
|
|
fn is_identifier(_s: &str) -> bool {
|
|
// FIXME: (arihant2math)
|
|
true
|
|
}
|
|
|
|
let is_valid = |path: &DOMString| {
|
|
path.is_empty() || is_identifier(path) || path.split(".").all(is_identifier)
|
|
};
|
|
|
|
match key_path {
|
|
StrOrStringSequence::StringSequence(paths) => {
|
|
if paths.is_empty() {
|
|
return false;
|
|
}
|
|
|
|
paths.iter().all(is_valid)
|
|
},
|
|
StrOrStringSequence::String(path) => is_valid(path),
|
|
}
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
// https://www.w3.org/TR/IndexedDB-2/#convert-value-to-key
|
|
fn convert_value_to_key(
|
|
cx: SafeJSContext,
|
|
input: HandleValue,
|
|
seen: Option<Vec<HandleValue>>,
|
|
) -> Result<IndexedDBKeyType, Error> {
|
|
// Step 1: If seen was not given, then let seen be a new empty set.
|
|
let _seen = seen.unwrap_or_default();
|
|
|
|
// Step 2: If seen contains input, then return invalid.
|
|
// FIXME:(rasviitanen)
|
|
// Check if we have seen this key
|
|
// Does not currently work with HandleValue,
|
|
// as it does not implement PartialEq
|
|
|
|
// Step 3
|
|
// FIXME:(rasviitanen) Accept buffer, array and date as well
|
|
if input.is_number() {
|
|
// FIXME:(rasviitanen) check for NaN
|
|
let key = structuredclone::write(cx, input, None).expect("Could not serialize key");
|
|
return Ok(IndexedDBKeyType::Number(key.serialized));
|
|
}
|
|
|
|
if input.is_string() {
|
|
let key = structuredclone::write(cx, input, None).expect("Could not serialize key");
|
|
return Ok(IndexedDBKeyType::String(key.serialized));
|
|
}
|
|
|
|
if input.is_object() {
|
|
rooted!(in(*cx) let object = input.to_object());
|
|
unsafe {
|
|
let mut built_in_class = ESClass::Other;
|
|
|
|
if !GetBuiltinClass(*cx, object.handle().into(), &mut built_in_class) {
|
|
return Err(Error::Data);
|
|
}
|
|
|
|
if let ESClass::Date = built_in_class {
|
|
// FIXME:(arihant2math) implement it the correct way
|
|
let key =
|
|
structuredclone::write(cx, input, None).expect("Could not serialize key");
|
|
return Ok(IndexedDBKeyType::Date(key.serialized.clone()));
|
|
}
|
|
|
|
if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
|
|
let key =
|
|
structuredclone::write(cx, input, None).expect("Could not serialize key");
|
|
// FIXME:(arihant2math) Return the correct type here
|
|
// it doesn't really matter at the moment...
|
|
return Ok(IndexedDBKeyType::Number(key.serialized.clone()));
|
|
}
|
|
|
|
if let ESClass::Array = built_in_class {
|
|
// FIXME:(arihant2math)
|
|
unimplemented!("Arrays as keys is currently unsupported");
|
|
}
|
|
}
|
|
}
|
|
|
|
Err(Error::Data)
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#evaluate-a-key-path-on-a-value
|
|
#[allow(unsafe_code)]
|
|
fn evaluate_key_path_on_value(
|
|
cx: SafeJSContext,
|
|
value: HandleValue,
|
|
mut return_val: MutableHandleValue,
|
|
key_path: &KeyPath,
|
|
) {
|
|
// The implementation is translated from gecko:
|
|
// https://github.com/mozilla/gecko-dev/blob/master/dom/indexedDB/KeyPath.cpp
|
|
return_val.set(*value);
|
|
|
|
rooted!(in(*cx) let mut target_object = ptr::null_mut::<JSObject>());
|
|
rooted!(in(*cx) let mut current_val = *value);
|
|
rooted!(in(*cx) let mut object = ptr::null_mut::<JSObject>());
|
|
|
|
let mut target_object_prop_name: Option<String> = None;
|
|
|
|
match key_path {
|
|
KeyPath::String(path) => {
|
|
// Step 3
|
|
let path_as_string = path.to_string();
|
|
let mut tokenizer = path_as_string.split('.').peekable();
|
|
|
|
while let Some(token) = tokenizer.next() {
|
|
if target_object.get().is_null() {
|
|
if token == "length" &&
|
|
tokenizer.peek().is_none() &&
|
|
current_val.is_string()
|
|
{
|
|
rooted!(in(*cx) let input_val = current_val.to_string());
|
|
unsafe {
|
|
let string_len = JS_GetStringLength(*input_val) as u64;
|
|
string_len.to_jsval(*cx, return_val);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if !current_val.is_object() {
|
|
// FIXME:(rasviitanen) Return a proper error
|
|
return;
|
|
}
|
|
|
|
object.handle_mut().set(current_val.to_object());
|
|
rooted!(in(*cx) let mut desc = PropertyDescriptor::default());
|
|
rooted!(in(*cx) let mut intermediate = UndefinedValue());
|
|
|
|
// So rust says that this value is never read, but it is.
|
|
#[allow(unused)]
|
|
let mut has_prop = false;
|
|
|
|
unsafe {
|
|
let prop_name_as_utf16: Vec<u16> = token.encode_utf16().collect();
|
|
let mut is_descriptor_none: bool = false;
|
|
let ok = JS_GetOwnUCPropertyDescriptor(
|
|
*cx,
|
|
object.handle().into(),
|
|
prop_name_as_utf16.as_ptr(),
|
|
prop_name_as_utf16.len(),
|
|
desc.handle_mut().into(),
|
|
&mut is_descriptor_none,
|
|
);
|
|
|
|
if !ok {
|
|
// FIXME:(arihant2math) Handle this
|
|
return;
|
|
}
|
|
|
|
if desc.hasWritable_() || desc.hasValue_() {
|
|
intermediate.handle_mut().set(desc.handle().value_);
|
|
has_prop = true;
|
|
} else {
|
|
// If we get here it means the object doesn't have the property or the
|
|
// property is available throuch a getter. We don't want to call any
|
|
// getters to avoid potential re-entrancy.
|
|
// The blob object is special since its properties are available
|
|
// only through getters but we still want to support them for key
|
|
// extraction. So they need to be handled manually.
|
|
unimplemented!("Blob tokens are not yet supported");
|
|
}
|
|
}
|
|
|
|
if has_prop {
|
|
// Treat undefined as an error
|
|
if intermediate.is_undefined() {
|
|
// FIXME:(rasviitanen) Throw/return error
|
|
return;
|
|
}
|
|
|
|
if tokenizer.peek().is_some() {
|
|
// ...and walk to it if there are more steps...
|
|
current_val.handle_mut().set(*intermediate);
|
|
} else {
|
|
// ...otherwise use it as key
|
|
return_val.set(*intermediate);
|
|
}
|
|
} else {
|
|
target_object.handle_mut().set(*object);
|
|
target_object_prop_name = Some(token.to_string());
|
|
}
|
|
}
|
|
|
|
if !target_object.get().is_null() {
|
|
// We have started inserting new objects or are about to just insert
|
|
// the first one.
|
|
// FIXME:(rasviitanen) Implement this piece
|
|
unimplemented!("keyPath tokens that requires insertion are not supported.");
|
|
}
|
|
} // All tokens processed
|
|
|
|
if !target_object.get().is_null() {
|
|
// If this fails, we lose, and the web page sees a magical property
|
|
// appear on the object :-(
|
|
unsafe {
|
|
let prop_name_as_utf16: Vec<u16> =
|
|
target_object_prop_name.unwrap().encode_utf16().collect();
|
|
#[allow(clippy::cast_enum_truncation)]
|
|
let mut succeeded = ObjectOpResult {
|
|
code_: ObjectOpResult_SpecialCodes::Uninitialized as usize,
|
|
};
|
|
if !JS_DeleteUCProperty(
|
|
*cx,
|
|
target_object.handle().into(),
|
|
prop_name_as_utf16.as_ptr(),
|
|
prop_name_as_utf16.len(),
|
|
&mut succeeded,
|
|
) {
|
|
// FIXME:(rasviitanen) Throw/return error
|
|
// return;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
KeyPath::StringSequence(_) => {
|
|
unimplemented!("String sequence keyPath is currently unsupported");
|
|
},
|
|
}
|
|
}
|
|
|
|
fn has_key_generator(&self) -> bool {
|
|
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
|
|
|
let operation = SyncOperation::HasKeyGenerator(
|
|
sender,
|
|
self.global().origin().immutable().clone(),
|
|
self.db_name.to_string(),
|
|
self.name.borrow().to_string(),
|
|
);
|
|
|
|
self.global()
|
|
.resource_threads()
|
|
.sender()
|
|
.send(IndexedDBThreadMsg::Sync(operation))
|
|
.unwrap();
|
|
|
|
receiver.recv().unwrap()
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#extract-a-key-from-a-value-using-a-key-path
|
|
fn extract_key(
|
|
cx: SafeJSContext,
|
|
input: HandleValue,
|
|
key_path: &KeyPath,
|
|
multi_entry: Option<bool>,
|
|
) -> Result<IndexedDBKeyType, Error> {
|
|
// Step 1: Evaluate key path
|
|
// FIXME:(rasviitanen) Do this propertly
|
|
rooted!(in(*cx) let mut r = UndefinedValue());
|
|
IDBObjectStore::evaluate_key_path_on_value(cx, input, r.handle_mut(), key_path);
|
|
|
|
if let Some(_multi_entry) = multi_entry {
|
|
// FIXME:(rasviitanen) handle multi_entry cases
|
|
unimplemented!("multiEntry keys are not yet supported");
|
|
} else {
|
|
IDBObjectStore::convert_value_to_key(cx, r.handle(), None)
|
|
}
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#object-store-in-line-keys
|
|
fn uses_inline_keys(&self) -> bool {
|
|
self.key_path.is_some()
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
|
|
fn put(
|
|
&self,
|
|
cx: SafeJSContext,
|
|
value: HandleValue,
|
|
key: HandleValue,
|
|
overwrite: bool,
|
|
can_gc: CanGc,
|
|
) -> Fallible<DomRoot<IDBRequest>> {
|
|
// Step 1: Let transaction be this object store handle's transaction.
|
|
let transaction = self
|
|
.transaction
|
|
.get()
|
|
.expect("No transaction in Object Store");
|
|
|
|
// Step 2: Let store be this object store handle's object store.
|
|
// This is resolved in the `execute_async` function.
|
|
|
|
// Step 3: If store has been deleted, throw an "InvalidStateError" DOMException.
|
|
// FIXME:(rasviitanen)
|
|
|
|
// Step 4-5: If transaction is not active, throw a "TransactionInactiveError" DOMException.
|
|
if !transaction.is_active() {
|
|
return Err(Error::TransactionInactive);
|
|
}
|
|
|
|
// Step 5: If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
|
|
if let IDBTransactionMode::Readonly = transaction.get_mode() {
|
|
return Err(Error::ReadOnly);
|
|
}
|
|
|
|
// Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException.
|
|
if !key.is_undefined() && self.uses_inline_keys() {
|
|
return Err(Error::Data);
|
|
}
|
|
|
|
// Step 7: If store uses out-of-line keys and has no key generator
|
|
// and key was not given, throw a "DataError" DOMException.
|
|
if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
|
|
return Err(Error::Data);
|
|
}
|
|
|
|
// Step 8: If key was given, then: convert a value to a key with key
|
|
let serialized_key: IndexedDBKeyType;
|
|
|
|
if !key.is_undefined() {
|
|
serialized_key = IDBObjectStore::convert_value_to_key(cx, key, None)?;
|
|
} else {
|
|
// Step 11: We should use in-line keys instead
|
|
if let Ok(kpk) = IDBObjectStore::extract_key(
|
|
cx,
|
|
value,
|
|
self.key_path.as_ref().expect("No key path"),
|
|
None,
|
|
) {
|
|
serialized_key = kpk;
|
|
} else {
|
|
// FIXME:(rasviitanen)
|
|
// Check if store has a key generator
|
|
// Check if we can inject a key
|
|
return Err(Error::Data);
|
|
}
|
|
}
|
|
|
|
let serialized_value =
|
|
structuredclone::write(cx, value, None).expect("Could not serialize value");
|
|
|
|
IDBRequest::execute_async(
|
|
self,
|
|
AsyncOperation::PutItem(serialized_key, serialized_value.serialized, overwrite),
|
|
None,
|
|
can_gc,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl IDBObjectStoreMethods<crate::DomTypeHolder> for IDBObjectStore {
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
|
|
fn Put(
|
|
&self,
|
|
cx: SafeJSContext,
|
|
value: HandleValue,
|
|
key: HandleValue,
|
|
) -> Fallible<DomRoot<IDBRequest>> {
|
|
self.put(cx, value, key, true, CanGc::note())
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-add
|
|
fn Add(
|
|
&self,
|
|
cx: SafeJSContext,
|
|
value: HandleValue,
|
|
key: HandleValue,
|
|
) -> Fallible<DomRoot<IDBRequest>> {
|
|
self.put(cx, value, key, false, CanGc::note())
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete
|
|
fn Delete(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
|
|
let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
|
|
serialized_query.and_then(|q| {
|
|
IDBRequest::execute_async(self, AsyncOperation::RemoveItem(q), None, CanGc::note())
|
|
})
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear
|
|
fn Clear(&self) -> Fallible<DomRoot<IDBRequest>> {
|
|
unimplemented!();
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get
|
|
fn Get(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
|
|
let serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
|
|
serialized_query.and_then(|q| {
|
|
IDBRequest::execute_async(self, AsyncOperation::GetItem(q), None, CanGc::note())
|
|
})
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey
|
|
// fn GetKey(&self, _cx: SafeJSContext, _query: HandleValue) -> DomRoot<IDBRequest> {
|
|
// unimplemented!();
|
|
// }
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getall
|
|
// fn GetAll(
|
|
// &self,
|
|
// _cx: SafeJSContext,
|
|
// _query: HandleValue,
|
|
// _count: Option<u32>,
|
|
// ) -> DomRoot<IDBRequest> {
|
|
// unimplemented!();
|
|
// }
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getallkeys
|
|
// fn GetAllKeys(
|
|
// &self,
|
|
// _cx: SafeJSContext,
|
|
// _query: HandleValue,
|
|
// _count: Option<u32>,
|
|
// ) -> DomRoot<IDBRequest> {
|
|
// unimplemented!();
|
|
// }
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count
|
|
fn Count(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
|
|
// Step 1
|
|
let transaction = self.transaction.get().expect("Could not get transaction");
|
|
|
|
// Step 2
|
|
// FIXME(arihant2math): investigate further
|
|
|
|
// Step 3
|
|
// FIXME(arihant2math): Cannot tell if store has been deleted
|
|
|
|
// Step 4
|
|
if !transaction.is_active() {
|
|
return Err(Error::TransactionInactive);
|
|
}
|
|
|
|
// Step 5
|
|
let _serialized_query = IDBObjectStore::convert_value_to_key(cx, query, None);
|
|
|
|
// Step 6
|
|
// match serialized_query {
|
|
// Ok(q) => IDBRequest::execute_async(&*self, AsyncOperation::Count(q), None),
|
|
// Err(e) => Err(e),
|
|
// }
|
|
Err(Error::NotSupported)
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name
|
|
fn Name(&self) -> DOMString {
|
|
self.name.borrow().clone()
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-setname
|
|
fn SetName(&self, value: DOMString) {
|
|
*self.name.borrow_mut() = value;
|
|
}
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-keypath
|
|
// fn KeyPath(&self, _cx: SafeJSContext, _val: MutableHandleValue) {
|
|
// unimplemented!();
|
|
// }
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-indexnames
|
|
// fn IndexNames(&self) -> DomRoot<DOMStringList> {
|
|
// unimplemented!();
|
|
// }
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-transaction
|
|
// fn Transaction(&self) -> DomRoot<IDBTransaction> {
|
|
// unimplemented!();
|
|
// }
|
|
|
|
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-autoincrement
|
|
fn AutoIncrement(&self) -> bool {
|
|
// FIXME(arihant2math): This is wrong
|
|
self.auto_increment
|
|
}
|
|
}
|