<commit_before>/*
 * Copyright (C) 2010-2012 <NAME>
 * <EMAIL>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.jkiss.dbeaver.model.impl.jdbc.cache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCExecutionContext;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCStatement;
import org.jkiss.dbeaver.model.impl.DBSObjectCache;
import org.jkiss.dbeaver.model.impl.DBSStructCache;
import org.jkiss.dbeaver.model.impl.SimpleObjectCache;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * JDBC structured objects cache.
 * Stores objects themselves and their child objects.
 */
public abstract class JDBCStructCache<
    OWNER extends DBSObject,
    OBJECT extends DBSObject,
    CHILD extends DBSObject>
    extends JDBCObjectCache<OWNER, OBJECT> implements DBSStructCache<OWNER, OBJECT, CHILD>
{
    static final Log log = LogFactory.getLog(JDBCStructCache.class);

    private final String objectNameColumn;
    private volatile boolean childrenCached = false;
    private final Map<OBJECT, SimpleObjectCache<OBJECT, CHILD>> childrenCache = new IdentityHashMap<OBJECT, SimpleObjectCache<OBJECT, CHILD>>();

    abstract protected JDBCStatement prepareChildrenStatement(JDBCExecutionContext context, OWNER owner, OBJECT forObject)
        throws SQLException;

    abstract protected CHILD fetchChild(JDBCExecutionContext context, OWNER owner, OBJECT parent, ResultSet dbResult)
        throws SQLException, DBException;

    protected JDBCStructCache(String objectNameColumn)
    {
        this.objectNameColumn = objectNameColumn;
    }

    /**
     * Reads children objects from database
     * @param monitor monitor
     * @param forObject object for which to read children. If null then reads children for all objects in this container.
     * @throws org.jkiss.dbeaver.DBException on error
     */
    public void loadChildren(DBRProgressMonitor monitor, OWNER owner, final OBJECT forObject)
        throws DBException
    {
        if ((forObject == null && this.childrenCached) ||
            (forObject != null && (!forObject.isPersisted() || isChildrenCached(forObject))) ||
            monitor.isCanceled())
        {
            return;
        }
        if (forObject == null) {
            super.loadObjects(monitor, owner);
        }

        JDBCExecutionContext context = (JDBCExecutionContext) owner.getDataSource().openContext(monitor, DBCExecutionPurpose.META, "Load child objects");
        try {
            Map<OBJECT, List<CHILD>> objectMap = new HashMap<OBJECT, List<CHILD>>();

            // Load columns
            JDBCStatement dbStat = prepareChildrenStatement(context, owner, forObject);
            try {
                dbStat.setFetchSize(DBConstants.METADATA_FETCH_SIZE);
                dbStat.executeStatement();
                JDBCResultSet dbResult = dbStat.getResultSet();
                try {
                    while (dbResult.next()) {
                        if (monitor.isCanceled()) {
                            break;
                        }
                        String objectName = JDBCUtils.safeGetString(dbResult, objectNameColumn);

                        OBJECT object = forObject;
                        if (object == null) {
                            object = super.getCachedObject(objectName);
                            if (object == null) {
                                log.debug("Object '" + objectName + "' not found");
                                continue;
                            }
                        }
                        if (isChildrenCached(object)) {
                            // Already read
                            continue;
                        }
                        CHILD child = fetchChild(context, owner, object, dbResult);
                        if (child == null) {
                            continue;
                        }

                        // Add to map
                        List<CHILD> children = objectMap.get(object);
                        if (children == null) {
                            children = new ArrayList<CHILD>();
                            objectMap.put(object, children);
                        }
                        children.add(child);
                    }

                    if (monitor.isCanceled()) {
                        return;
                    }

                    // All children are read. Now assign them to parents
                    for (Map.Entry<OBJECT, List<CHILD>> colEntry : objectMap.entrySet()) {
                        cacheChildren(colEntry.getKey(), colEntry.getValue());
                    }
                    if (forObject == null) {
                        if (objectMap.isEmpty()) {
                            // Nothing was read. May be it means empty list of children
                            // but possibly this feature is not supported [JDBC: SQLite]
                        } else {
                            // Now set empty column list for other tables
                            for (OBJECT tmpObject : getObjects(monitor, owner)) {
                                if (!isChildrenCached(tmpObject) && !objectMap.containsKey(tmpObject)) {
                                    cacheChildren(tmpObject, new ArrayList<CHILD>());
                                }
                            }
                            this.childrenCached = true;
                        }
                    } else if (!objectMap.containsKey(forObject)) {
                        cacheChildren(forObject, new ArrayList<CHILD>());
                    }
                }
                finally {
                    dbResult.close();
                }
            }
            finally {
                dbStat.close();
            }
        }
        catch (SQLException ex) {
            throw new DBException(ex);
        }
        finally {
            context.close();
        }
    }

    @Override
    public void clearCache()
    {
        this.clearChildrenCache(null);
        super.clearCache();
    }

    public DBSObjectCache<OBJECT, CHILD> getChildrenCache(final OBJECT forObject)
    {
        synchronized (childrenCache) {
            return childrenCache.get(forObject);
        }
    }

    public Collection<CHILD> getChildren(DBRProgressMonitor monitor, OWNER owner, final OBJECT forObject)
        throws DBException
    {
        loadChildren(monitor, owner, forObject);
        synchronized (childrenCache) {
            SimpleObjectCache<OBJECT, CHILD> nestedCache = childrenCache.get(forObject);
            return nestedCache == null ? null : nestedCache.getObjects(monitor, null);
        }
    }

    public CHILD getChild(DBRProgressMonitor monitor, OWNER owner, final OBJECT forObject, String objectName)
        throws DBException
    {
        loadChildren(monitor, owner, forObject);
        synchronized (childrenCache) {
            SimpleObjectCache<OBJECT, CHILD> nestedCache = childrenCache.get(forObject);
            return nestedCache == null ? null : nestedCache.getObject(monitor, null, objectName);
        }
    }

    public void clearChildrenCache(OBJECT forParent)
    {
        synchronized (childrenCache) {
            if (forParent != null) {
                this.childrenCache.remove(forParent);
            } else {
                this.childrenCache.clear();
            }
        }
    }

    private boolean isChildrenCached(OBJECT parent)
    {
        synchronized (childrenCache) {
            return childrenCache.containsKey(parent);
        }
    }

    private void cacheChildren(OBJECT parent, List<CHILD> children)
    {
        synchronized (childrenCache) {
            SimpleObjectCache<OBJECT, CHILD> nestedCache = childrenCache.get(parent);
            if (nestedCache == null) {
                nestedCache = new SimpleObjectCache<OBJECT, CHILD>();
                childrenCache.put(parent, nestedCache);
            }
            nestedCache.setCache(children);
        }
    }

}<commit_msg>Cleanup children cache on object remove<commit_after>/*
 * Copyright (C) 2010-2012 <NAME>
 * <EMAIL>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.jkiss.dbeaver.model.impl.jdbc.cache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.exec.DBCExecutionPurpose;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCExecutionContext;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCStatement;
import org.jkiss.dbeaver.model.impl.DBSObjectCache;
import org.jkiss.dbeaver.model.impl.DBSStructCache;
import org.jkiss.dbeaver.model.impl.SimpleObjectCache;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * JDBC structured objects cache.
 * Stores objects themselves and their child objects.
 */
public abstract class JDBCStructCache<
    OWNER extends DBSObject,
    OBJECT extends DBSObject,
    CHILD extends DBSObject>
    extends JDBCObjectCache<OWNER, OBJECT> implements DBSStructCache<OWNER, OBJECT, CHILD>
{
    static final Log log = LogFactory.getLog(JDBCStructCache.class);

    private final String objectNameColumn;
    private volatile boolean childrenCached = false;
    private final Map<OBJECT, SimpleObjectCache<OBJECT, CHILD>> childrenCache = new IdentityHashMap<OBJECT, SimpleObjectCache<OBJECT, CHILD>>();

    abstract protected JDBCStatement prepareChildrenStatement(JDBCExecutionContext context, OWNER owner, OBJECT forObject)
        throws SQLException;

    abstract protected CHILD fetchChild(JDBCExecutionContext context, OWNER owner, OBJECT parent, ResultSet dbResult)
        throws SQLException, DBException;

    protected JDBCStructCache(String objectNameColumn)
    {
        this.objectNameColumn = objectNameColumn;
    }

    /**
     * Reads children objects from database
     * @param monitor monitor
     * @param forObject object for which to read children. If null then reads children for all objects in this container.
     * @throws org.jkiss.dbeaver.DBException on error
     */
    public void loadChildren(DBRProgressMonitor monitor, OWNER owner, final OBJECT forObject)
        throws DBException
    {
        if ((forObject == null && this.childrenCached) ||
            (forObject != null && (!forObject.isPersisted() || isChildrenCached(forObject))) ||
            monitor.isCanceled())
        {
            return;
        }
        if (forObject == null) {
            super.loadObjects(monitor, owner);
        }

        JDBCExecutionContext context = (JDBCExecutionContext) owner.getDataSource().openContext(monitor, DBCExecutionPurpose.META, "Load child objects");
        try {
            Map<OBJECT, List<CHILD>> objectMap = new HashMap<OBJECT, List<CHILD>>();

            // Load columns
            JDBCStatement dbStat = prepareChildrenStatement(context, owner, forObject);
            try {
                dbStat.setFetchSize(DBConstants.METADATA_FETCH_SIZE);
                dbStat.executeStatement();
                JDBCResultSet dbResult = dbStat.getResultSet();
                try {
                    while (dbResult.next()) {
                        if (monitor.isCanceled()) {
                            break;
                        }
                        String objectName = JDBCUtils.safeGetString(dbResult, objectNameColumn);

                        OBJECT object = forObject;
                        if (object == null) {
                            object = super.getCachedObject(objectName);
                            if (object == null) {
                                log.debug("Object '" + objectName + "' not found");
                                continue;
                            }
                        }
                        if (isChildrenCached(object)) {
                            // Already read
                            continue;
                        }
                        CHILD child = fetchChild(context, owner, object, dbResult);
                        if (child == null) {
                            continue;
                        }

                        // Add to map
                        List<CHILD> children = objectMap.get(object);
                        if (children == null) {
                            children = new ArrayList<CHILD>();
                            objectMap.put(object, children);
                        }
                        children.add(child);
                    }

                    if (monitor.isCanceled()) {
                        return;
                    }

                    // All children are read. Now assign them to parents
                    for (Map.Entry<OBJECT, List<CHILD>> colEntry : objectMap.entrySet()) {
                        cacheChildren(colEntry.getKey(), colEntry.getValue());
                    }
                    if (forObject == null) {
                        if (objectMap.isEmpty()) {
                            // Nothing was read. May be it means empty list of children
                            // but possibly this feature is not supported [JDBC: SQLite]
                        } else {
                            // Now set empty column list for other tables
                            for (OBJECT tmpObject : getObjects(monitor, owner)) {
                                if (!isChildrenCached(tmpObject) && !objectMap.containsKey(tmpObject)) {
                                    cacheChildren(tmpObject, new ArrayList<CHILD>());
                                }
                            }
                            this.childrenCached = true;
                        }
                    } else if (!objectMap.containsKey(forObject)) {
                        cacheChildren(forObject, new ArrayList<CHILD>());
                    }
                }
                finally {
                    dbResult.close();
                }
            }
            finally {
                dbStat.close();
            }
        }
        catch (SQLException ex) {
            throw new DBException(ex);
        }
        finally {
            context.close();
        }
    }

    @Override
    public void removeObject(OBJECT object)
    {
        super.removeObject(object);
        clearChildrenCache(object);
    }

    @Override
    public void clearCache()
    {
        this.clearChildrenCache(null);
        super.clearCache();
    }

    public DBSObjectCache<OBJECT, CHILD> getChildrenCache(final OBJECT forObject)
    {
        synchronized (childrenCache) {
            return childrenCache.get(forObject);
        }
    }

    public Collection<CHILD> getChildren(DBRProgressMonitor monitor, OWNER owner, final OBJECT forObject)
        throws DBException
    {
        loadChildren(monitor, owner, forObject);
        synchronized (childrenCache) {
            SimpleObjectCache<OBJECT, CHILD> nestedCache = childrenCache.get(forObject);
            return nestedCache == null ? null : nestedCache.getObjects(monitor, null);
        }
    }

    public CHILD getChild(DBRProgressMonitor monitor, OWNER owner, final OBJECT forObject, String objectName)
        throws DBException
    {
        loadChildren(monitor, owner, forObject);
        synchronized (childrenCache) {
            SimpleObjectCache<OBJECT, CHILD> nestedCache = childrenCache.get(forObject);
            return nestedCache == null ? null : nestedCache.getObject(monitor, null, objectName);
        }
    }

    public void clearChildrenCache(OBJECT forParent)
    {
        synchronized (childrenCache) {
            if (forParent != null) {
                this.childrenCache.remove(forParent);
            } else {
                this.childrenCache.clear();
            }
        }
    }

    private boolean isChildrenCached(OBJECT parent)
    {
        synchronized (childrenCache) {
            return childrenCache.containsKey(parent);
        }
    }

    private void cacheChildren(OBJECT parent, List<CHILD> children)
    {
        synchronized (childrenCache) {
            SimpleObjectCache<OBJECT, CHILD> nestedCache = childrenCache.get(parent);
            if (nestedCache == null) {
                nestedCache = new SimpleObjectCache<OBJECT, CHILD>();
                childrenCache.put(parent, nestedCache);
            }
            nestedCache.setCache(children);
        }
    }

}
{'fraction_non_alphanumeric': 0.08057873059457175, 'fraction_numerical': 0.002534586545569754, 'mean_word_length': 1.6986320889142206, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2775160', 'n_tokens_mistral': 4775, 'n_tokens_neox': 4508, 'n_words': 1174}