【问题标题】:How do I use Room's prepackagedDatabaseCallback?如何使用 Rooms 预打包的数据库回调?
【发布时间】:2021-11-28 09:09:50
【问题描述】:

对于 2.3.0-alpha03 版本的房间有一个 prepackagedDatabaseCallback 它说:-

  • 此回调将在复制预包 DB 之后但在 Room 有机会打开它之前调用,因此在调用 RoomDatabase.Callback 方法之前。此回调可用于更新预打包 DB 架构以满足 Room 的架构验证。

那么我该如何使用它来规避 Invalid Schema expected .... found ....?

我可以用它来介绍触发器吗,因为房间没有用于创建触发器的注释?

注意这是一个 ask, and answer, your own question

【问题讨论】:

    标签: android-room android-room-prepackageddatabase


    【解决方案1】:

    那么我该如何使用它来规避 Invalid Schema expected .... found ....?

    以下虽然冗长但显示了一个相应地更正架构的示例。

    我可以用它来介绍触发器吗,因为房间没有用于创建触发器的注释?

    是的,虽然这实际上没有经过测试,但该示例适合创建触发器。

    重新评分示例的说明 首先,应该注意的是,最初查看回调时存在问题。这些问题现已得到解决需要 2.4.0-beta02 或更高版本。问题是:-

    1. 不允许在预打包的数据库中使用版本 0(通常是这种情况)
    2. 将空数据库传递给 PrePackagedDatabaseCallback
    3. 在 PrePackagedDatabaseCallback Lost/Undone 中应用的数据库更新
    • 应用的修复是

    • 在 PrepackagedDatabaseCallback 调用期间正确打开预打包数据库。

    • 实际的预包数据库没有被打开,因为在创建临时打开帮助程序时只使用了文件名而不是完整路径。如果数据库的名称是路径(以“/”开头),框架打开帮助程序只会打开现有文件,否则它会创建一个新文件。此更改通过使用绝对文件路径作为名称来修复它。

    流程

    当控制权传递给回调时,资产数据库已被复制。因此,理论上,这只是改变 TABLES 和 VIEWS(如果有)以匹配房间期望的模式的问题。

    经常试图将房间的期望与房间的发现相匹配,这会让一些人感到沮丧。下面的示例将克服次要/小/经常遗漏的问题。它的工作原理是:-

    1. 删除除表之外的所有非 SQLite 或非 Android 组件(即复制的资产数据库中的视图、索引和触发器),
    2. 重命名表格以允许创建房间预期的表格,
    3. 使用 Room 的预期架构创建新表,
      1. 从 Room 生成的 java 中复制
    4. 将重命名(资产)表中的数据复制到新创建的表中
      1. 已使用 INSERT OR IGNORE,因此约束冲突,例如 NOT NULL、UNIQUE、CHECK 不会导致异常(将 INSERT OR IGNORE 更改为仅 INSERT 会失败)
    5. 创建 Room 期望的其他组件(视图、索引和触发器(注意 Room 仅具有 FTS 表的触发器))。
    6. 删除现在多余的重命名表
    7. 执行 VACUUM 清理数据库,
    8. 最后添加任何触发器。

    它确实期望源(资产)具有正确顺序的列,空值不包含在房间具有 NOT NULL 约束的列中。但是,由于使用了 INSERT OR IGNORE,因此不会插入此类行,而是导致异常。

    除了从生成的 java 中复制代码之外,该过程是自动化的,应该可以处理大多数/许多资产而无需修改。

    守则

    绝大多数代码都在@Database 类OtherDatabase 中。请注意,这应该可以处理许多数据库,需要进行一些调整(请参阅 cmets 了解要进行的更改):-

    @SuppressLint({"Range"}) /*<<<<< used due to bug/issue with getColumnIndex introduced with SDK 31 */
    @Database(
            entities = {Person.class, Company.class, CompanyPersonMap.class}, //<<<<< CHANGED ACCORDINGLY
            version = OtherDatabase.DATABASE_VERSION, /* note due to shared usage of DBVERSION OtherDatabase used <<<<< CHANGE ACCORDINGLY */
            exportSchema = false /* change to suit */
    )
    @TypeConverters({AllTypeConverters.class})
    abstract class OtherDatabase extends RoomDatabase {
        public static final String DATABASE_NAME = "otherprepackageddatabasecallbacktest.db"; //<<<<< CHANGE AS REQUIRED
        public static final int DATABASE_VERSION = 1; //<<<<< CHANGE AS REQUIRED
        public static final String ASSET_FILE_NAME = "prepackageddatabasecallbacktest.db"; //<<<<< CHANGED AS REQUIRED
        /**
         *  sqlite_master table and column names !!!!!DO NOT CHANGE!!!!!
         */
        private static final String SQLITE_MASTER_TABLE_NAME = "sqlite_master";
        private static final String SQLITE_MASTER_COL_NAME = "name";
        private static final String SQLITE_MASTER_COL_TYPE = "type";
        private static final String SQLITE_MASTER_COL_TABLE_NAME = "tbl_name";
        private static final String SQLITE_MASTER_COL_SQL = "sql";
    
        abstract AllDao getAllDao(); //<<<<< CHANGE ACCORDINGLY
        private static volatile OtherDatabase instance = null; //<<<<< CHANGE ACCORDINGLY
        public static OtherDatabase /*<<<< CHANGE ACCORDINGLY */ getInstance(Context context) {
            if (instance == null) {
                instance = Room.databaseBuilder(context, OtherDatabase.class, OtherDatabase.DATABASE_NAME)
                        .allowMainThreadQueries() /*<<<<< USED FOR BREVITY CONVENIENCE */
                        .createFromAsset(ASSET_FILE_NAME, prePkgDbCallback) /* 2nd parameter is the THE CALL BACK to invoke */
                        .build();
            }
            return instance;
        }
    
        /* THE CALLBACK  */
        static final PrepackagedDatabaseCallback prePkgDbCallback = new PrepackagedDatabaseCallback() {
            final static String assetTablePrefix = "asset_"; /* prefix used for renamed tables - should not need to be changed */
            private List<SQLiteMasterComponent> sqLiteMasterComponentArray; /* store for sqlite_master extract */
            @Override
            public void onOpenPrepackagedDatabase(@NonNull SupportSQLiteDatabase db) {
                super.onOpenPrepackagedDatabase(db);
                sqLiteMasterComponentArray = buildComponentsList(db); /* gets relevant rows from sqlite_master */
                dropNonTableComponents(sqLiteMasterComponentArray,db); /* everything except the tables */
                renameTableComponents(sqLiteMasterComponentArray, assetTablePrefix,db); /* rename the tables using prefix */
                /*<<<<< TAILOR (the db.execSQL's below) AS APPROPRIATE - SEE COMMENTS THAT FOLLOW >>>>>*/
                /* copied from the @Database classes generated java e.g. leave indexes till later
                    _db.execSQL("CREATE TABLE IF NOT EXISTS `Person` (`personid` INTEGER, `firstName` TEXT, `lastName` TEXT, `middleNames` TEXT, `dateOfBirth` INTEGER, PRIMARY KEY(`personid`))");
                    _db.execSQL("CREATE INDEX IF NOT EXISTS `index_Person_firstName` ON `Person` (`firstName`)");
                    _db.execSQL("CREATE INDEX IF NOT EXISTS `index_Person_lastName` ON `Person` (`lastName`)");
                    _db.execSQL("CREATE TABLE IF NOT EXISTS `company` (`companyid` INTEGER, `companyName` TEXT, `city` TEXT, `state` TEXT, `country` TEXT, `notes` TEXT, PRIMARY KEY(`companyid`))");
                    _db.execSQL("CREATE TABLE IF NOT EXISTS `company_person_map` (`companyid_map` INTEGER NOT NULL, `personid_map` INTEGER NOT NULL, PRIMARY KEY(`companyid_map`, `personid_map`), FOREIGN KEY(`companyid_map`) REFERENCES `company`(`companyid`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`personid_map`) REFERENCES `Person`(`personid`) ON UPDATE CASCADE ON DELETE CASCADE )");
                    _db.execSQL("CREATE INDEX IF NOT EXISTS `index_company_person_map_personid_map` ON `company_person_map` (`personid_map`)");
                 */
                /* Create the tables as per Room definitions - ***** CREATE TABLES COPIED FROM GENERATED JAVA *****
                    only indexes, views, triggers (for FTS) should be done after the data has been copied so :-
                        data is loaded faster as no index updates are required.
                        triggers don't get triggered when loading the data which could result in unexpected results
                 */
                db.execSQL("CREATE TABLE IF NOT EXISTS `Person` (`personid` INTEGER, `firstName` TEXT, `lastName` TEXT, `middleNames` TEXT, `dateOfBirth` INTEGER, PRIMARY KEY(`personid`));");
                db.execSQL("CREATE TABLE IF NOT EXISTS `company` (`companyid` INTEGER, `companyName` TEXT, `city` TEXT, `state` TEXT, `country` TEXT, `notes` TEXT, PRIMARY KEY(`companyid`))");
                db.execSQL("CREATE TABLE IF NOT EXISTS `company_person_map` (`companyid_map` INTEGER NOT NULL, `personid_map` INTEGER NOT NULL, PRIMARY KEY(`companyid_map`, `personid_map`), FOREIGN KEY(`companyid_map`) REFERENCES `company`(`companyid`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`personid_map`) REFERENCES `Person`(`personid`) ON UPDATE CASCADE ON DELETE CASCADE )");
    
                copyData(sqLiteMasterComponentArray, assetTablePrefix,db); /* copy the data from the renamed asset tables to the newly created Room tables */
    
                /* Create the other Room components - ***** CREATE ? COPIED FROM GENERATED JAVA *****
                    Now that data has been copied create other Room components indexes, views and triggers
                    (triggers would only be for FTS (full text search))
                    again sourced from generated Java
                 */
                db.execSQL("CREATE INDEX IF NOT EXISTS `index_Person_firstName` ON `Person` (`firstName`)");
                db.execSQL("CREATE INDEX IF NOT EXISTS `index_Person_lastName` ON `Person` (`lastName`)");
                db.execSQL("CREATE INDEX IF NOT EXISTS `index_company_person_map_personid_map` ON `company_person_map` (`personid_map`)");
                dropRenamedTableComponents(sqLiteMasterComponentArray, assetTablePrefix,db); /* done with the renamed tables so drop them */
                db.execSQL("VACUUM"); /* cleanup the database */
                createTriggers(sqLiteMasterComponentArray,db); /* create any triggers */
            }
        };
    
        static int dropNonTableComponents(List<SQLiteMasterComponent> components, SupportSQLiteDatabase db) {
            int rv = 0;
            for(SQLiteMasterComponent c: components) {
                if (!c.type.equals("table") ) {
                    db.execSQL("DROP " +  c.type + " IF EXISTS " + c.name);
                    rv++;
                }
            }
            return rv;
        }
    
        static int dropRenamedTableComponents(List<SQLiteMasterComponent> components, String prefix, SupportSQLiteDatabase db) {
            int rv = 0;
            int maxForeignKeyCount = 0;
            for (SQLiteMasterComponent c: components) {
                if (c.type.equals("table") && c.foreignKeyCount > maxForeignKeyCount) {
                    maxForeignKeyCount = c.foreignKeyCount;
                }
            }
            for (int i= maxForeignKeyCount; i >= 0; i--) {
                for (SQLiteMasterComponent c: components) {
                    if (c.type.equals("table") && c.foreignKeyCount == i) {
                        db.execSQL("DROP " + c.type + " IF EXISTS " + prefix + c.name);
                        rv++;
                    }
                }
            }
            return rv;
        }
    
        static int renameTableComponents(List<SQLiteMasterComponent> components, String prefix, SupportSQLiteDatabase db) {
            int rv = 0;
            db.execSQL("PRAGMA foreign_keys = ON"); // Just in case turn foreign keys on
            for(SQLiteMasterComponent c: components) {
                if (c.type.equals("table")) {
                    db.execSQL("ALTER TABLE " + c.name + " RENAME TO " + prefix + c.name);
                    rv++;
                }
            }
            return rv;
        }
    
        /*
            NOTE tables with fewest Foreign Key definitions done first
            NOTE makes an assumption that this will not result in FK conflicts
            TODO should really be amended to ensure that a table with FK's is only attempted when all of it's parent tables have been loaded
         */
        static int copyData(List<SQLiteMasterComponent> components, String prefix, SupportSQLiteDatabase db) {
            int rv = 0;
            int maxForeignKeyCount = 0;
            for (SQLiteMasterComponent c: components) {
                if (c.type.equals("table") && c.foreignKeyCount > 0) {
                    maxForeignKeyCount = c.foreignKeyCount;
                }
            }
            for (int i=0; i <= maxForeignKeyCount; i++) {
                for (SQLiteMasterComponent c: components) {
                    if (c.type.equals("table") && c.foreignKeyCount == i) {
                        db.execSQL("INSERT OR IGNORE INTO " + c.name + " SELECT * FROM " + prefix + c.name + ";");
                        rv++;
                    }
                }
            }
            return rv;
        }
    
        static int createTriggers(List<SQLiteMasterComponent> components, SupportSQLiteDatabase db) {
            int rv = 0;
            for (SQLiteMasterComponent c: components) {
                if (c.type.equals("trigger")) {
                    //TODO should really check if the sql includes IF NOT EXISTSand if not add IF NOT EXISTS
                    db.execSQL(c.sql);
                    rv++;
                }
            }
            return rv;
        }
    
        /**
         * Build the list of required SQLiteMasterComponents to save having to access
         * sqlite_master many times.
         * @param db the SupportSQliteDatabase to access
         * @return the list of SQliteMasterComponents extracted
         */
        @SuppressLint("Range")
        static List<SQLiteMasterComponent> buildComponentsList(SupportSQLiteDatabase db) {
            final String FOREIGN_KEY_FLAG_COLUMN =  "foreign_key_flag";
            ArrayList<SQLiteMasterComponent> rv = new ArrayList<>();
            Cursor csr = db.query("SELECT *," +
                    /* Column to indicate wherther or not FK constraints appear to have been defined
                     *  NOTE!! can be fooled
                     *       e.g. if a column is defined as `my  badly named FOREIGN KEY column ` ....
                     * */
                    "(" +
                    "instr(" + SQLITE_MASTER_COL_SQL + ",'FOREIGN KEY') > 0) + " +
                    "(instr(" + SQLITE_MASTER_COL_SQL + ",' REFERENCES ')> 0) " +
                    "AS " + FOREIGN_KEY_FLAG_COLUMN + " " +
                    "FROM " + SQLITE_MASTER_TABLE_NAME + " " +
                    /* do not want any sqlite tables or android tables included */
                    "WHERE lower(" + SQLITE_MASTER_COL_NAME + ") NOT LIKE 'sqlite_%' AND lower(" + SQLITE_MASTER_COL_NAME + ") NOT LIKE 'android_%'");
            while (csr.moveToNext()) {
                SQLiteMasterComponent component = new SQLiteMasterComponent(
                        csr.getString(csr.getColumnIndex(SQLITE_MASTER_COL_NAME)),
                        csr.getString(csr.getColumnIndex(SQLITE_MASTER_COL_TYPE)),
                        csr.getString(csr.getColumnIndex(SQLITE_MASTER_COL_TABLE_NAME)),
                        csr.getString(csr.getColumnIndex(SQLITE_MASTER_COL_SQL)),
                        csr.getInt(csr.getColumnIndex(FOREIGN_KEY_FLAG_COLUMN)),
                        0);
                if (csr.getInt(csr.getColumnIndex(FOREIGN_KEY_FLAG_COLUMN)) > 0) {
                    component.foreignKeyCount = component.getForeignKeyCount(db);
                }
                rv.add(component);
            }
            csr.close();
            return (List<SQLiteMasterComponent>) rv;
        }
    
        /**
         *  Class to hold a row from sqlite_master
         */
        private static class SQLiteMasterComponent {
            private String name;
            private String type;
            private String owningTable;
            private String sql;
            private int foreignKeyFlag = 0;
            private int foreignKeyCount = 0;
    
            SQLiteMasterComponent(String name, String type, String owningTable, String sql, int foreignKeyFlag, int foreignKeyCount) {
                this.name = name;
                this.type = type;
                this.owningTable = owningTable;
                this.sql = sql;
                this.foreignKeyFlag = foreignKeyFlag;
                this.foreignKeyCount = foreignKeyCount;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public String getType() {
                return type;
            }
    
            public void setType(String type) {
                this.type = type;
            }
    
            public String getOwningTable() {
                return owningTable;
            }
    
            public void setOwningTable(String owningTable) {
                this.owningTable = owningTable;
            }
    
            public String getSql() {
                return sql;
            }
    
            public void setSql(String sql) {
                this.sql = sql;
            }
    
            public int getForeignKeyFlag() {
                return foreignKeyFlag;
            }
    
            public void setForeignKeyFlag(int foreignKeyFlag) {
                this.foreignKeyFlag = foreignKeyFlag;
            }
    
            public boolean isForeignKey() {
                return this.foreignKeyFlag > 0;
            }
    
            public int getForeignKeyCount() {
                return foreignKeyCount;
            }
    
            public void setForeignKeyCount(int foreignKeyCount) {
                this.foreignKeyCount = foreignKeyCount;
            }
    
            /**
             * Retrieve the number of rows returned by PRAGMA foreign_key_list
             * @param db    The SupportSQLiteDatabase to access
             * @return      The number of rows i.e. number of Foreign Key constraints
             */
            private int getForeignKeyCount(SupportSQLiteDatabase db) {
                int rv =0;
                Cursor csr = db.query("SELECT count(*) FROM pragma_foreign_key_list('" + this.name + "');");
                if (csr.moveToFirst()) {
                    rv = csr.getInt(0);
                }
                csr.close();
                return rv;
            }
        }
    }
    

    工作示例

    资产数据库

    资产数据库包含 3 个表,personcompanycompany_person_map

    DDL是

    CREATE TABLE person (personid INTEGER PRIMARY KEY, firstName TEXT, lastName TEXT, middleNames TEXT, dateOfBirth DATE);
    

    房间预期:-

    CREATE TABLE IF NOT EXISTS `Person` (`personid` INTEGER, `firstName` TEXT, `lastName` TEXT, `middleNames` TEXT, `dateOfBirth` INTEGER, PRIMARY KEY(`personid`))
    
    • 请注意 dateOfBirth 列的 DATE v INTEGER 类型,哪个房间不接受这样 预期的 ... 找到 ...。

    公司 DDL是

    CREATE TABLE company (companyid INTEGER PRIMARY KEY, companyName TEXT, city TEXT, state TEXT, country TEXT, notes TEXT);
    

    房间预期:-

    CREATE TABLE IF NOT EXISTS `company` (`companyid` INTEGER, `companyName` TEXT, `city` TEXT, `state` TEXT, `country` TEXT, `notes` TEXT, PRIMARY KEY(`companyid`))
    
    • 两者都是可比的房间会接受

    company_person_map DDL 是

    CREATE TABLE company_person_map (
    companyid_map INTEGER NOT NULL REFERENCES company(companyid) ON DELETE CASCADE ON UPDATE CASCADE,
    personid_map INTEGER NOT NULL REFERENCES person(personid) ON DELETE CASCADE ON UPDATE CASCADE, 
    PRIMARY KEY (companyid_map, personid_map));
    

    房间预期:-

    CREATE TABLE IF NOT EXISTS `company_person_map` (
    `companyid_map` INTEGER NOT NULL,`personid_map` INTEGER NOT NULL, 
    PRIMARY KEY(`companyid_map`, `personid_map`), 
    FOREIGN KEY(`companyid_map`) REFERENCES `company`(`companyid`) ON UPDATE CASCADE ON DELETE CASCADE , 
    FOREIGN KEY(`personid_map`) REFERENCES `Person`(`personid`) ON UPDATE CASCADE ON DELETE CASCADE )
    
    • 虽然它们有很大不同,但它们具有可比性,房间会接受

    资产表包含以下数据:-

    公司

    company_person_map

    • 人员 11 未映射到公司。

    AllDao

    • 没有使用任何插入,也没有使用所有查询。

    调用Activity是:-

    public class MainActivity extends AppCompatActivity {
    
        OtherDatabase dbOther;
        AllDao daoOther;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            dbOther = OtherDatabase.getInstance(this);
            daoOther = dbOther.getAllDao();
            for(Person p: daoOther.getAllPeople()) {
                Log.d("DBINFO",
                        "Person is " + p.firstName
                                + " " + p.middleNames
                                + " " + p.lastName
                                + " Date of Birth is " + p.dateOfBirth
                                + " ID is " + p.id
                );
            }
            for(CompanyWithPeople cwp: dao.getCompanyWithPeople()) {
                logCompany(cwp.company,"DBINFO","");
                for(Person p: cwp.person) {
                    logPerson(p,"DBINFO","\n\t");
                }
            }
        }
    
        void logCompany(Company c, String tag, String lineFeed) {
            Log.d(tag,lineFeed + "Company is " + c.companyName
                    + " Location is " + c.city + ", " + c.state + ", " + c.country
                    + " ID is " + c.companyId
                    + "\n\t Notes: " + c.notes
            );
        }
    
        void logPerson(Person p, String tag, String lineFeed) {
            Log.d(tag, lineFeed + p.firstName + " " + p.middleNames
                            + " " + p.lastName + " Date of Birth is " + p.dateOfBirth
                            + " ID is " + p.id
            );
        }
    }
    

    结果

    2021-11-28 19:56:01.928 D/DBINFO: Person is Robert John Smith Date of Birth is Sat Dec 27 17:46:33 GMT+10:00 1969 ID is 1
    2021-11-28 19:56:01.929 D/DBINFO: Person is Julie Mary Armstrong Date of Birth is Mon Jan 12 23:22:04 GMT+10:00 1970 ID is 2
    2021-11-28 19:56:01.929 D/DBINFO: Person is Andrea Susan Stewart Date of Birth is Mon Jan 05 04:56:09 GMT+10:00 1970 ID is 3
    2021-11-28 19:56:01.929 D/DBINFO: Person is Mary Belinda Allway Date of Birth is Mon Jan 12 00:15:21 GMT+10:00 1970 ID is 4
    2021-11-28 19:56:01.929 D/DBINFO: Person is Lisa Elizabeth Brooks Date of Birth is Sat Jan 03 03:51:21 GMT+10:00 1970 ID is 5
    2021-11-28 19:56:01.930 D/DBINFO: Person is Stephen Colin Cobbs Date of Birth is Tue Jan 06 14:01:55 GMT+10:00 1970 ID is 6
    2021-11-28 19:56:01.930 D/DBINFO: Person is Breane Cath Davidson Date of Birth is Thu Jan 01 22:30:14 GMT+10:00 1970 ID is 7
    2021-11-28 19:56:01.930 D/DBINFO: Person is Trevor Arthur Frankston Date of Birth is Sat Jan 10 03:47:02 GMT+10:00 1970 ID is 8
    2021-11-28 19:56:01.930 D/DBINFO: Person is George Howard Erksine Date of Birth is Sun Jan 11 00:47:02 GMT+10:00 1970 ID is 9
    2021-11-28 19:56:01.930 D/DBINFO: Person is Uriah Stanley Jefferson Date of Birth is Mon Dec 29 19:11:31 GMT+10:00 1969 ID is 10
    2021-11-28 19:56:01.931 D/DBINFO: Person is Valerie Alana Singleton Date of Birth is Thu Jan 01 11:45:07 GMT+10:00 1970 ID is 11
    2021-11-28 19:56:01.931 D/DBINFO: Person is Vladimir Oscar Whitworth Date of Birth is Sat Jan 10 00:29:45 GMT+10:00 1970 ID is 12
    2021-11-28 19:56:01.936 D/DBINFO: Company is Allbright Construction Location is Sydney, NSW, Australia ID is 1
             Notes: 
    2021-11-28 19:56:01.936 D/DBINFO:   Julie Mary Armstrong Date of Birth is Mon Jan 12 23:22:04 GMT+10:00 1970 ID is 2
    2021-11-28 19:56:01.936 D/DBINFO:   Mary Belinda Allway Date of Birth is Mon Jan 12 00:15:21 GMT+10:00 1970 ID is 4
    2021-11-28 19:56:01.937 D/DBINFO:   Stephen Colin Cobbs Date of Birth is Tue Jan 06 14:01:55 GMT+10:00 1970 ID is 6
    2021-11-28 19:56:01.937 D/DBINFO:   Trevor Arthur Frankston Date of Birth is Sat Jan 10 03:47:02 GMT+10:00 1970 ID is 8
    2021-11-28 19:56:01.937 D/DBINFO:   Uriah Stanley Jefferson Date of Birth is Mon Dec 29 19:11:31 GMT+10:00 1969 ID is 10
    2021-11-28 19:56:01.937 D/DBINFO:   Vladimir Oscar Whitworth Date of Birth is Sat Jan 10 00:29:45 GMT+10:00 1970 ID is 12
    2021-11-28 19:56:01.937 D/DBINFO: Company is Dextronics Location is Slough, Berkshire, England ID is 2
             Notes: 
    2021-11-28 19:56:01.937 D/DBINFO:   Robert John Smith Date of Birth is Sat Dec 27 17:46:33 GMT+10:00 1969 ID is 1
    2021-11-28 19:56:01.938 D/DBINFO:   Andrea Susan Stewart Date of Birth is Mon Jan 05 04:56:09 GMT+10:00 1970 ID is 3
    2021-11-28 19:56:01.938 D/DBINFO:   Lisa Elizabeth Brooks Date of Birth is Sat Jan 03 03:51:21 GMT+10:00 1970 ID is 5
    2021-11-28 19:56:01.938 D/DBINFO:   Breane Cath Davidson Date of Birth is Thu Jan 01 22:30:14 GMT+10:00 1970 ID is 7
    2021-11-28 19:56:01.938 D/DBINFO:   George Howard Erksine Date of Birth is Sun Jan 11 00:47:02 GMT+10:00 1970 ID is 9
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多